web-dev-qa-db-ja.com

一度に複数の例外をキャッチ?

単にSystem.Exceptionをキャッチすることはお勧めできません。代わりに、「既知の」例外のみをキャッチする必要があります。

さて、これは時々、不必要な反復コードにつながります。例えば:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

私は疑問に思う:両方の例外をキャッチしてWebId = Guid.Empty呼び出しを一度だけ呼び出す方法はありますか?

与えられた例はそれが GUID だけであるので、かなり単純です。しかし、オブジェクトを何度も変更するコードを想像してみてください。そのうちの1つの操作が期待どおりに失敗した場合は、objectを「リセット」します。しかし、予期しない例外があったとしても、それをさらに高くしたいのです。

1889
Michael Stum

System.Exceptionをキャッチして型をオンにする

catch (Exception ex)            
{                
    if (ex is FormatException || ex is OverflowException)
    {
        WebId = Guid.Empty;
        return;
    }

    throw;
}
1935
Joseph Daigle

編集:私は、C#6.0の時点で、例外フィルターが完全に素晴らしい方法であると言っている他の人と同意します:catch (Exception ex) when (ex is ... || ex is ... )

ただし、私はまだ1本の長い行のレイアウトが嫌いで、個人的に次のようにコードをレイアウトします。私は理解を改善すると信じているので、これは美的であると同時に機能的だと思います。反対する人もいます:

catch (Exception ex) when (
    ex is ...
    || ex is ...
    || ex is ...
)

オリジナル:

私はここのパーティーに少し遅れていることを知っていますが、聖なる煙...

追跡にまっすぐ進むと、この種の回答は以前の回答を複製しますが、いくつかの例外タイプに対して共通のアクションを実行し、1つのメソッドのスコープ内で全体を整然とした状態に保ちたい場合は、ラムダを使用しないでください/ closure/inline関数は次のようなことをしますか?つまり、そのクロージャーを、あちこちで利用できる別の方法にしたいだけだということに気付く可能性はかなり高いです。しかし、その後、実際に残りのコードを構造的に変更することなく、それを行うのは非常に簡単です。右?

private void TestMethod ()
{
    Action<Exception> errorHandler = ( ex ) => {
        // write to a log, whatever...
    };

    try
    {
        // try some stuff
    }
    catch ( FormatException  ex ) { errorHandler ( ex ); }
    catch ( OverflowException ex ) { errorHandler ( ex ); }
    catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}

warning:少し皮肉/皮肉が先になっている)不思議に思わずにはいられない。

try
{
    // try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}

...この次のコードの匂いのいくつかのクレイジーなバリエーションで、私はあなたがいくつかのキーストロークを保存しているふりをするだけの例を意味します。

// sorta sucks, let's be honest...
try
{
    // try some stuff
}
catch( Exception ex )
{
    if (ex is FormatException ||
        ex is OverflowException ||
        ex is ArgumentNullException)
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

確かに、自動的に読みやすくなるわけではないからです。

確かに、最初の例から/* write to a log, whatever... */ return;の3つの同一のインスタンスを残しました。

しかし、それは私のポイントのようなものです。関数/メソッドのことを聞いたことがありますか?真剣に。共通のErrorHandler関数を作成し、同様に、各catchブロックから呼び出します。

私に尋ねると、2番目の例(ifおよびisキーワードを使用)は、可読性が大幅に低下すると同時に、プロジェクトのメンテナンスフェーズでエラーが発生しやすくなります。

プログラミングに比較的慣れていない人にとっては、メンテナンスフェーズはプロジェクトの全ライフタイムの98.7%以上を占めることになり、メンテナンスを行う貧弱なシュマックはほぼ間違いなくあなた以外の誰かになります。そして、彼らがあなたの名前を呪う仕事に彼らの時間の50%を費やす非常に良いチャンスがあります。

もちろん、FxCopはあなたに向かってksえますので、also正確にZipを実行する属性をコードに追加する必要があります実行中のプログラムで、99.9%のケースでフラグ付けが完全に正しいという問題を無視するようにFxCopに指示するためだけにあります。そして、申し訳ありませんが、私は間違っているかもしれませんが、その「無視」属性は実際にアプリにコンパイルされませんか?

ifテスト全体を1行に入れると読みやすくなりますか?そうは思いません。つまり、1行にコードを追加すると「実行速度が向上する」と強く主張する別のプログラマーがいました。しかし、もちろん彼は非常に熱狂的なナッツでした。インタプリタまたはコンパイラがその長い行を個別の1行ごとの命令文に分割する方法を、彼に説明しようとしています(まっすぐに-これは挑戦的でした)-彼が先に行った場合の結果と本質的に同じですコンパイラを上手にしようとする代わりに、コードを読み取り可能にしただけです。しかし、私は脱線します。

これから1か月または2か月、さらに3種類の例外タイプを追加すると、どれだけless読み取り可能になりますか? (回答:lot可読性が低くなります)。

本当に大きなポイントの1つは、私たち全員が毎日見ているテキストソースコードをフォーマットするポイントのほとんどが、コードの実行時に実際に何が起こっているかを他の人間に本当に明白にすることです。コンパイラはソースコードをまったく異なるものに変換し、コードの書式設定スタイルをあまり気にすることができなかったためです。だから、オールオンワンラインも完全にダメです。

ただ言って...

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}
489
Craig

他の人が指摘したように、catchブロック内にifステートメントを記述して、何が起こっているのかを判断できます。 C#6は例外フィルターをサポートしているため、次のように機能します。

try { … }
catch (Exception e) when (MyFilter(e))
{
    …
}

MyFilterメソッドは次のようになります。

private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

あるいは、これはすべてインラインで実行できます(whenステートメントの右側は、ブール式である必要があります)。

try { … }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    …
}

これは、ifブロック内からcatchステートメントを使用する場合とは異なり、例外フィルターを使用するとスタックを巻き戻しません

Visual Studio 2015 をダウンロードして、これを確認できます。

Visual Studio 2013を引き続き使用する場合は、次のnugetパッケージをインストールできます。

インストールパッケージMicrosoft.Net.Compilers

執筆時点では、これにはC#6のサポートが含まれます

このパッケージを参照すると、システムにインストールされたバージョンではなく、パッケージに含まれている特定のバージョンのC#およびVisual Basicコンパイラを使用してプロジェクトがビルドされます。

317
Joe

残念ながらC#にはありません。例外フィルターを使用する必要があり、C#ではMSILのその機能が公開されていないためです。しかし、VB.NETはこの機能を持っています。

Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException

あなたができることはエラー時のコードをカプセル化するために無名関数を使うことであり、それからそれらの特定のcatchブロックでそれを呼び出すことです:

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}
185
Greg Beech

完全を期すために、 .NET 4.0 からコードを次のように書き換えることができます。

Guid.TryParse(queryString["web"], out WebId);

TryParse は例外をスローせず、形式が正しくない場合はfalseを返し、WebIdをGuid.Emptyに設定します。


C#7 なので、別の行に変数を入れないでください。

Guid.TryParse(queryString["web"], out Guid webId);

戻りタプルを解析するためのメソッドを作成することもできます。これは、.NET Frameworkではまだバージョン4.6では使用できません。

(bool success, Guid result) TryParseGuid(string input) =>
    (Guid.TryParse(input, out Guid result), result);

そしてこれらを次のように使います。

WebId = TryParseGuid(queryString["web"]).result;
// or
var Tuple = TryParseGuid(queryString["web"]);
WebId = Tuple.success ? Tuple.result : DefaultWebId;

この無用な答えに対する次の無用な更新は、out-parametersの分解がC#12で実装されたときにやってきた:)

129
Athari

アプリケーションをC#6にアップグレードできれば幸運です。新しいC#バージョンは例外フィルタを実装しました。だからあなたはこれを書くことができます:

catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
    WebId = Guid.Empty;
}

このコードは以下のコードと同じだと考える人もいます

catch (Exception ex) {                
    if (ex is FormatException || ex is OverflowException) {
        WebId = Guid.Empty;
    }
    throw;
}

しかしそうではありません。実際、これはC#6の唯一の新機能で、以前のバージョンではエミュレートできません。まず、再スローはキャッチをスキップするよりも多くのオーバーヘッドを意味します。第二に、それは意味的に同等ではありません。コードをデバッグしているとき、新しい機能はスタックをそのまま保持します。この機能がなければ、クラッシュダンプはあまり役に立ちませんし、役に立たなくなります。

CodePlexでこれに関する の議論を参照してください 。そして の違いを示す例

71
Maniero

例外フィルタは6+ C#で使用できるようになりました。できるよ

try
{
       WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
     WebId = Guid.Empty;
}

C#の7.0以降では、パターンがあまりにも一致でこれを組み合わせることができます

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae )
{
   //do something with members of ae, say ae.InnerExceptions
}
53
Mat J

C#7で、 Michael Stumからの答え は、switch文の読みやすさを保ちながら改善できます。

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
18
Fabian

c#6では、推奨されるアプローチは例外フィルタを使用することです、これは例です:

 try
 {
      throw new OverflowException();
 }
 catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException))
 {
       // this will execute iff e is DividedByZeroEx or OverflowEx
       Console.WriteLine("E");
 }
18
SHM

受け入れられた答えは、CodeAnalysis / FxCop が一般的な例外タイプをキャッチしているという事実について不平を言うことを除けば、許容できるように思えます。

また、is演算子はパフォーマンスをわずかに低下させる可能性があります。

CA1800:不必要にキャストしないでくださいは「代わりに 'as'演算子の結果をテストすることを検討してください」と言っています。別に例外があります。

とにかく、これが私がすることです:

bool exThrown = false;

try
{
    // Something
}
catch (FormatException) {
    exThrown = true;
}
catch (OverflowException) {
    exThrown = true;
}

if (exThrown)
{
    // Something else
}
18
Matt

これはMattの答えの変形です(これは少しきれいだと思います)...メソッドを使用します。

public void TryCatch(...)
{
    try
    {
       // something
       return;
    }
    catch (FormatException) {}
    catch (OverflowException) {}

    WebId = Guid.Empty;
}

他の例外はスローされ、コードWebId = Guid.Empty;はヒットしません。他の例外でプログラムをクラッシュさせたくない場合は、他の2つのキャッチの後にこれを追加してください。

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}
17
bsara

Joseph Daigle's Answer は良い解決策ですが、私は次のような構造が少し整頓されていて間違いが起こりにくいと感じました。

catch(Exception ex)
{   
    if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

式を反転することには、いくつかの利点があります。

  • Returnステートメントは必要ありません
  • コードはネストされていません
  • ジョセフの解決法では表現から切り離されている 'throw'や 'return'ステートメントを忘れてしまう危険はありません。

それは単一行に圧縮することさえできます(それほどきれいではありませんが)。

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

編集: C#6.0の 例外フィルタリング は構文を少しきれいにし、 他の多くの利点があります 現在のどのソリューションよりも優れています。 (最も顕著なのはスタックを無傷のままにしておく)

これは、C#6.0の構文を使用した場合の同じ問題の様子です。

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}
17
Stefan T

@ミケル

あなたのコードの少し改訂されたバージョン:

catch (Exception ex)
{
   Type exType = ex.GetType();
   if (exType == typeof(System.FormatException) || 
       exType == typeof(System.OverflowException)
   {
       WebId = Guid.Empty;
   } else {
      throw;
   }
}

文字列の比較は醜くて遅いです。

16
FlySwat

どうですか?

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
13
Maurice

注意と警告: さらに別の種類の、機能的なスタイル。

リンクにあるものはあなたの質問に直接答えるものではありませんが、それを次のように拡張するのは簡単です。

static void Main() 
{ 
    Action body = () => { ...your code... };

    body.Catch<InvalidOperationException>() 
        .Catch<BadCodeException>() 
        .Catch<AnotherException>(ex => { ...handler... })(); 
}

(基本的に自分自身を返す空のCatchオーバーロードをもう1つ提供する)

これに対するより大きな質問は なぜ です。私は、コストがここでの利益を上回るとは思いません:)

11
nawfal
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}
11

更新2015-12-15:C#6については https://stackoverflow.com/a/22864936/1718702 を参照してください。それは、よりクリーンでそして今やその言語の標準となっています。

より洗練された解決策 を一度キャッチして例外をフィルタリングすることを望む人々を対象とし、私は以下に示すように拡張メソッドを使います。

私は自分のライブラリにこの拡張子を既に持っていました。もともと他の目的のために書かれていましたが、例外のtypeチェックには完璧に機能しました。加えて、imho、それはたくさんの||ステートメントよりもきれいに見えます。また、受け入れられた答えとは異なり、私は明示的な例外処理を好むので、ex is ...は派生クラスがその親型に割り当て可能であるため望ましくない動作をしていました。).

使用方法

if (ex.GetType().IsAnyOf(
    typeof(FormatException),
    typeof(ArgumentException)))
{
    // Handle
}
else
    throw;

IsAnyOf.cs拡張子(依存関係については完全なエラー処理の例を参照)

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }
    }
}

完全なエラー処理の例(新しいコンソールアプリケーションへのコピー&ペースト)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;

namespace IsAnyOfExceptionHandlerSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // High Level Error Handler (Log and Crash App)
            try
            {
                Foo();
            }
            catch (OutOfMemoryException ex)
            {
                Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
                Console.ReadKey();
            }
        }

        static void Foo()
        {
            // Init
            List<Action<string>> TestActions = new List<Action<string>>()
            {
                (key) => { throw new FormatException(); },
                (key) => { throw new ArgumentException(); },
                (key) => { throw new KeyNotFoundException();},
                (key) => { throw new OutOfMemoryException(); },
            };

            // Run
            foreach (var FooAction in TestActions)
            {
                // Mid-Level Error Handler (Appends Data for Log)
                try
                {
                    // Init
                    var SomeKeyPassedToFoo = "FooParam";

                    // Low-Level Handler (Handle/Log and Keep going)
                    try
                    {
                        FooAction(SomeKeyPassedToFoo);
                    }
                    catch (Exception ex)
                    {
                        if (ex.GetType().IsAnyOf(
                            typeof(FormatException),
                            typeof(ArgumentException)))
                        {
                            // Handle
                            Console.WriteLine("ex was {0}", ex.GetType().Name);
                            Console.ReadKey();
                        }
                        else
                        {
                            // Add some Debug info
                            ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
                            throw;
                        }
                    }
                }
                catch (KeyNotFoundException ex)
                {
                    // Handle differently
                    Console.WriteLine(ex.Message);

                    int Count = 0;
                    if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
                        foreach (var Key in ex.Data.Keys)
                            Console.WriteLine(
                                "[{0}][\"{1}\" = {2}]",
                                Count, Key, ex.Data[Key]);

                    Console.ReadKey();
                }
            }
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }

        /// <summary>
        /// Validates if any passed in parameter is equal to null.
        /// </summary>
        /// <param name="p_parameters">Parameters to test for Null.</param>
        /// <returns>True if one or more parameters are null.</returns>
        public static bool IsAnyNull(params object[] p_parameters)
        {
            p_parameters
                .CannotBeNullOrEmpty("p_parameters");

            foreach (var item in p_parameters)
                if (item == null)
                    return true;

            return false;
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void CannotBeNull(this object p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(
                        string.Format("Parameter \"{0}\" cannot be null.",
                        p_name), default(Exception));
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));

            if (p_parameter.Count <= 0)
                throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
        {
            if (string.IsNullOrEmpty(p_parameter))
                throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
        }
    }
}

2つのサンプルNUnit単体テスト

Exception型の一致動作は正確です(つまり、子ISはその親の型のいずれとも一致しません)。

using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;

namespace UnitTests.Common.Fluent_Validations
{
    [TestFixture]
    public class IsAnyOf_Tests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
        {
            Action TestMethod = () => { throw new ArgumentNullException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
                    typeof(FormatException),
                    typeof(KeyNotFoundException)))
                {
                    // Handle expected Exceptions
                    return;
                }

                //else throw original
                throw;
            }
        }

        [Test, ExpectedException(typeof(OutOfMemoryException))]
        public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
        {
            Action TestMethod = () => { throw new OutOfMemoryException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(OutOfMemoryException),
                    typeof(StackOverflowException)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}
10
HodlDwon

私はこれらの答えが表面に触れただけのように感じたので、私はもう少し深く掘ることを試みました。

ですから、私たちが本当にやりたいことは、コンパイルできないことです。

// Won't compile... damn
public static void Main()
{
    try
    {
        throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentOutOfRangeException)
    catch (IndexOutOfRangeException) 
    {
        // ... handle
    }

これが欲しいのは、例外ハンドラがプロセスの後半で必要になるものをキャッチしたくないからです。確かに、例外をキャッチして「if」で何をすればいいのかをチェックできますが、正直なところ、実際にはそれは望ましくありません。 (FxCop、デバッガの問題、醜さ)

それでは、なぜこのコードはコンパイルされないのでしょうか。そして、どうやってそれをできるようにハッキングできるのでしょうか。

コードを見てみると、本当にやりたいことは呼び出しを転送することです。ただし、MS Partition IIによると、IL例外ハンドラブロックはこのようには動作しません。これは、「例外」オブジェクトのタイプが異なる可能性があることを意味するため、この場合は意味があります。

あるいはコードでそれを書くために、私たちはコンパイラにこのようなことをするように頼みます(まあそれは完全に正しいというわけではありません、しかしそれは私が推測する最も近い可能なものです):

// Won't compile... damn
try
{
    throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
    goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
    Console.WriteLine("Handle!");
}

これがコンパイルされない理由は非常に明白です: '$ exception'オブジェクトがどんな型と値を持つか(ここでは変数 'e'に格納されます)?コンパイラにこれを処理させる方法は、両方の例外の共通基本型が 'Exception'であることに注意し、両方の例外を含む変数にそれを使用してから、捕捉された2つの例外のみを処理することです。これがILで実装される方法はVB.Netで利用可能な「フィルタ」としてあります。

C#で機能させるためには、正しい '例外'基本型を持つ一時変数が必要です。コードの流れを制御するために、いくつかブランチを追加することができます。これが行きます:

    Exception ex;
    try
    {
        throw new ArgumentException(); // for demo purposes; won't be caught.
        goto noCatch;
    }
    catch (ArgumentOutOfRangeException e) {
        ex = e;
    }
    catch (IndexOutOfRangeException e) {
        ex = e;
    }

    Console.WriteLine("Handle the exception 'ex' here :-)");
    // throw ex ?

noCatch:
    Console.WriteLine("We're done with the exception handling.");

これに対する明らかな不利な点は、適切に再スローすることができないことです。そして、正直言って、それがかなり醜い解決策であることです。分岐の除去を実行することで醜さを少し修正することができます。これは解決策をわずかに良くします。

Exception ex = null;
try
{
    throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
    ex = e;
}
catch (IndexOutOfRangeException e)
{
    ex = e;
}
if (ex != null)
{
    Console.WriteLine("Handle the exception here :-)");
}

それだけで '再投げ'を残します。これが機能するためには、 'catch'ブロック内で処理を実行できる必要があります。この機能を実現する唯一の方法は、 'Exception'オブジェクトをキャッチすることです。

この時点で、オーバーロード解決を使用して、または例外を処理するために、さまざまな種類の例外を処理する別の関数を追加できます。どちらにも欠点があります。はじめに、ヘルパー関数を使ってそれを行う方法は次のとおりです。

private static bool Handle(Exception e)
{
    Console.WriteLine("Handle the exception here :-)");
    return true; // false will re-throw;
}

public static void Main()
{
    try
    {
        throw new OutOfMemoryException();
    }
    catch (ArgumentException e)
    {
        if (!Handle(e)) { throw; }
    }
    catch (IndexOutOfRangeException e)
    {
        if (!Handle(e)) { throw; }
    }

    Console.WriteLine("We're done with the exception handling.");

もう1つの解決策は、Exceptionオブジェクトをキャッチしてそれに応じて処理することです。上記の文脈に基づくこれのための最も文字通りの翻訳はこれです:

try
{
    throw new ArgumentException();
}
catch (Exception e)
{
    Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
    if (ex != null)
    {
        Console.WriteLine("Handle the exception here :-)");
        // throw ?
    }
    else 
    {
        throw;
    }
}

だから結論として:

  • 再スローしたくない場合は、正しい例外をキャッチしてそれらを一時的に格納することを検討します。
  • ハンドラが単純で、コードを再利用したい場合は、おそらくヘルパー関数を導入することが最善の解決策です。
  • 再スローしたい場合は、コードを 'Exception'キャッチハンドラに入れるしかありません。これにより、FxCopとデバッガのキャッチされていない例外が中断されます。
7
atlaste

それで、あなたはすべての例外スイッチの中でたくさんのコードを繰り返していますか?メソッドを抽出するようなことは、神の考えでしょうか。

だからあなたのコードはこれに来る:

MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }

void Reset(MyClass instance) { /* reset the state of the instance */ }

私はなぜ誰もそのコードの重複に気づかなかったのだろうか。

C#6からは、他の人がすでに述べたように、さらに exception-filters があります。だからあなたはこれに上記のコードを変更することができます:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}
6
HimBromBeere

これはすべてのC#開発者が最終的に直面する古典的な問題です。

あなたの質問を2つの質問に分けましょう。最初、

一度に複数の例外をキャッチできますか。

一言で言えば、いいえ。

これは次の質問につながります、

同じcatch()ブロック内で複数の例外タイプをキャッチできない場合に、重複コードを記述しないようにする方法

フォールバック値を構成するのが安価であるあなたの特定のサンプルを考えると、私はこれらのステップに従うのが好きです:

  1. WebIdをフォールバック値に初期化します。
  2. 一時的な変数で新しいGuidを構築します。
  3. WebIdを完全に構成された一時変数に設定します。これをtry {}ブロックの最後のステートメントにします。

そのため、コードは次のようになります。

try
{
    WebId = Guid.Empty;
    Guid newGuid = new Guid(queryString["web"]);
    // More initialization code goes here like 
    // newGuid.x = y;
    WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}

何らかの例外がスローされた場合、WebIdは半構成値に設定されず、Guid.Emptyのままになります。

フォールバック値を構築するのに費用がかかり、値をリセットする方がはるかに安い場合は、リセットコードを独自の関数に移動します。

try
{
    WebId = new Guid(queryString["web"]);
    // More initialization code goes here.
}
catch (FormatException) {
    Reset(WebId);
}
catch (OverflowException) {
    Reset(WebId);
}
6
Jeffrey Rennie

キャッチ節の内側にないコードの他の部分で行うのと同じように、共通コードをメソッドに入れるなど、コードを単純にしておくことをお勧めします。

例えば。:

try
{
    // ...
}
catch (FormatException)
{
    DoSomething();
}
catch (OverflowException)
{
    DoSomething();
}

// ...

private void DoSomething()
{
    // ...
}

単純で美しい パターンを見つけようとしているのです

4
Żubrówka

この長いスレッドに私の短い答えを追加したかったのです。言及されていないことはcatchステートメントの優先順位です。より具体的には、キャッチしようとしている各タイプの例外の範囲を知っておく必要があります。

たとえば、 Exception として "catch-all"例外を使用すると、他のすべてのcatchステートメントの前に置かれ、明らかにコンパイラエラーが発生します。ただし、順序を逆にすると、catchステートメントを連鎖できます。アンチパターン私は)あなたが一番下にキャッチオール 例外 タイプを置くことができ、これはあなたのtry..catchブロックでより高いのを満たすことができなかったすべての例外をキャプチャします:

            try
            {
                // do some work here
            }
            catch (WebException ex)
            {
                // catch a web excpetion
            }
            catch (ArgumentException ex)
            {
                // do some stuff
            }
            catch (Exception ex)
            {
                // you should really surface your errors but this is for example only
                throw new Exception("An error occurred: " + ex.Message);
            }

このMSDNドキュメントをレビューすることを強くお勧めします。

例外階層

4
Tahir Khalid

私はそれをする1つの方法を見つけましたが、これは The Daily WTF のための材料のように見えます。

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
2
Michael Stum

ここで言及する価値があります。複数の組み合わせ(Exception errorとexception.message)に対応できます。

TextBox、TextBlockまたはCheckBoxのいずれかのコンテンツを使用して、データグリッド内のコントロールオブジェクトをキャストしようとしたときに、ユースケースシナリオに遭遇しました。この場合、返された例外は同じですが、メッセージはさまざまです。

try
{
 //do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
} 
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
} 
0
George

私は最短の答えを提案したい(もう1つの 機能的なスタイル ):

        Catch<FormatException, OverflowException>(() =>
            {
                WebId = new Guid(queryString["web"]);
            },
            exception =>
            {
                WebId = Guid.Empty;
            });

これには、System.Actionのように、いくつかの "Catch"メソッドオーバーロードを作成する必要があります。

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
    }

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2, TException3>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
    }

そしてあなたが望むだけの数で。しかし、あなたは一度それをする必要があり、あなたはあなたのすべてのプロジェクトでそれを使用することができます(あるいは、あなたが我々がそれを使用することができるnugetパッケージを作成したなら)。

そしてCatchManyの実装:

    [DebuggerNonUserCode]
    public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
        params Type[] exceptionTypes)
    {
        try
        {
            tryBlock();
        }
        catch (Exception exception)
        {
            if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
            else throw;
        }
    }

pSコードの簡素化のためにnullチェックをしていません。パラメータ検証を追加することを検討してください。

p.s.2 catchから値を返したい場合は、同じCatchメソッドを実行する必要がありますが、パラメータにActionの代わりに戻り値とFuncを使用します。

0
Eugene Gorbovoy