例外フィルターとは何ですか?
例外フィルターは、
catch
特定の条件を設定できる新しいC#6機能です。 このブロックは、指定された条件が満たされた場合にのみ実行されます。 わずかなコードで構文を説明します。
public void Main() { try { throw new Exception("E2"); } catch(Exception ex) when(ex.Message == "E1") { Console.WriteLine("caught E1"); } catch(Exception ex) when(ex.Message == "E2") { Console.WriteLine("caught E2"); } }
これは本当に新しい機能ですか?
C#については、はい。 ただし、例外フィルターのサポートは、ILおよびVB.NETに長く存在していました。 F#言語でさえ、例外パターンマッチングと呼ばれるメカニズムを使用してこれらのフィルターをサポートしています。
しかし、通常の条件演算子を使用してこの機能を取得できますか?
論理的に-はい、しかし根本的な違いがあります。 条件が
catch
ブロック内にある場合、例外が最初に
catch
、次に条件がチェックされます。 例外フィルターは、例外をキャッチする前に条件をチェックします。 条件が満たされない場合、
catch
ブロックはスキップされ、.NETは次の
catch
ブロックの検討に進みます。
それで、違いは何ですか?
例外をキャッチすると、巻き戻されたスタックがあります。 重要な例外情報を失います。
throw ex
代わりに
catch
ブロック内で
throw
を実行すると、スタックを保存するという誤解があります。 実際、人々は
StackTrace
例外
StackTrace
のみを考え、CLRスタック自体については考えないということです。 例を見てみましょう。 自分で実行しようとする場合は、Visual Studioの設定で、例外をキャッチするための[例外で中断]チェックボックスが無効になっていることを確認してください([デバッグ]-> [例外]-> [すべてオフ])。
例外をキャッチしてログに記録し、それ以外は何もしないという一般的なシナリオを考えてみましょう。 次の画像は、例外がスローされたときにデバッガーが停止する場所を示しています。 デバッガーの停止線と[ローカル]ウィンドウに注意してください。
ここで、例外フィルターを使用して例を少し書き直しましょう。
Log
メソッドを常に
false
を返し、
catch
ブロック内に配置する代わりに例外フィルターを使用して
Log
を
Log
しましょう。 繰り返しますが、デバッガーの停止行とデバッガーウィンドウに注意してください ( 注:投稿と例は更新されましたが、画像は同じままです
catch (if(Log()))
代わりに、
catch when (Log())
読み取ります :
翻訳者から:過去に例外フィルターの構文が少し変更されたため、元のイメージは古くなっています:以前は
if
キーワードを使用して記述されていましたが、現在は新しい
when
キーワードに置き換えられています。 動機は次の図によく示されています。
例外が発生した正確な場所に関する情報に加えて、2番目の例のLocalsウィンドウには、ローカル変数
localVariable
も表示できます。これは、
catch
ブロック内のスタックにないため、最初の例では使用できません。 これは、クラッシュダンプで確認できる内容に対応しています。
また、
catch
ブロックを入力した場合、他のブロックは入力しません。 条件を分析し、再び破棄しないことを決定した場合、他の
catch
ブロックに入ることができなくなります。 例外フィルターの場合、条件が満たされていなくても、別のブロックに入ろうとするために残りの
catch
条件をチェックすることはできません。
期待される動作
したがって、例外フィルターで条件を指定できます。
catch
blockは、条件が満たされた場合にのみ実行されます。 また、
bool
関数を条件として使用できます。 しかし、条件自体が例外をスローするとどうなりますか? 予想される動作は次のとおりです。例外は無視され、条件は偽と見なされます。 次のコードを検討してください。
class Program { public static void Main() { TestExceptionFilters(); } public static void TestExceptionFilters() { try { throw new Exception("Original Exception"); } catch (Exception ex) when (MyCondition()) { Console.WriteLine(ex); } } public static bool MyCondition() { throw new Exception("Condition Exception"); } }
"Original Exception"
スローする場合、
catch
に入る前に
catch
MyCondition条件がチェックされます。 ただし、この条件自体は例外をスローするため、無視する必要があり、条件はfalseと見なされる必要があります。 そのため、未処理の例外が発生します。
System.Exception: Original Exception
予期しない動作
奇妙な例の時が来ました。 上記のコードを変更して、
TestExceptionFilters()
メソッドを直接呼び出す代わりに、このメソッドがリフレクションを通じて呼び出されるようにします。 関数の呼び出し方法は異なりますが、期待される動作は同じままです。
class Program { public static void Main() { var targetMethod = typeof(Program).GetMethod("TestExceptionFilters"); targetMethod.Invoke(null, new object[0]); } public static void TestExceptionFilters() { try { throw new Exception("Original exception"); } catch (Exception ex) when (MyCondition()) { Console.WriteLine(ex); } } public static bool MyCondition() { throw new Exception("Condition Exception"); } }
このコードを実行しましょう。 予想どおり、未処理の例外が発生しますが、例外のタイプのみが異なります。
System.Exception: Condition Exception
したがって、例外のタイプは、関数を呼び出した方法によって異なります。 このバグについて、GitHubで問題が発生しました( リフレクション経由で呼び出された場合、例外フィルターは異なる動作をし、フィルターは例外をスローします )。 執筆時点では、バグはまだCoreCLRに存在しています。 うまくいけば誰かが彼をすぐに修正します。
翻訳者から:この投稿は、サイトwww.volatileread.comからの2つの投稿を一度に合成した翻訳です: C#6例外フィルターとC#6例外フィルターの 予測できない動作 と、それらが構文シュガーよりもはるかに優れている方法