C#6での予期しない例外フィルターの動作

例外フィルターとは何ですか?



例外フィルターは、 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例外フィルターの 予測できない動作 と、それらが構文シュガーよりもはるかに優れている方法



All Articles