web-dev-qa-db-ja.com

try catchブロックの「when」キーワードはifステートメントと同じですか?

C#6.0では、「when」キーワードが導入されましたが、catchブロックで例外をフィルタリングできるようになりました。しかし、これはcatchブロック内のifステートメントと同じではありませんか?もしそうなら、それは単なる構文糖ではありませんか、何かが欠けていますか?

たとえば、「when」キーワードを使用したtry catchブロック:

try { … } 
catch (WebException ex) when ex.Status == WebExceptionStatus.Timeout {
   //do something
}
catch (WebException ex) when ex.Status== WebExceptionStatus.SendFailure {
   //do something
}
catch (Exception caught) {…}

または

try { … } 
catch (WebException ex) {
   if(ex.Status == WebExceptionStatus.Timeout) {
      //do something
   }
}
catch (WebException ex) {
   if(ex.Status == WebExceptionStatus.SendFailure) {
      //do something
   }
}
catch (Exception caught) {…}
56
Undeadparade

あなたがすでに持っているいくつかの素晴らしい答えに加えて、例外フィルターとcatchブロックの「if」の間に非常に重要な違いがあります:フィルタは、内部のfinallyブロックの前に実行されます

以下を考慮してください。

void M1()
{
  try { N(); } catch (MyException) { if (F()) C(); }
}
void M2()
{
  try { N(); } catch (MyException) when F() { C(); }
}
void N()
{
  try { MakeAMess(); DoSomethingDangerous(); } 
  finally { CleanItUp(); }
}

呼び出しの順序は、M1とM2で異なります。

M1が呼び出されたとします。 MakeAMess()を呼び出すN()を呼び出します。混乱が生じます。その後、DoSomethingDangerous()はMyExceptionをスローします。ランタイムは、それを処理できるcatchブロックが存在するかどうかを確認します。 finallyブロックはCleanItUp()を実行します。混乱はクリーンアップされます。制御はcatchブロックに渡されます。そして、catchブロックはF()を呼び出し、次にC()を呼び出します。

M2はどうですか? MakeAMess()を呼び出すN()を呼び出します。混乱が生じます。その後、DoSomethingDangerous()はMyExceptionをスローします。ランタイムは、それを処理できるcatchブロックがあるかどうかを確認します。ランタイムは、F()を呼び出して、catchブロックが処理できるかどうかを確認します。finallyブロックはCleanItUp()を実行し、制御はcatchに渡され、C()が呼び出されます。

違いに気づきましたか? M1の場合、F()は]が混乱をクリーンアップした後と呼ばれ、M2の場合は before 混乱がクリーンアップされますF()がその正確さのために混乱がないことに依存している場合、M1をM2のようにリファクタリングすると大きな問題になります!

ここには単なる正確性の問題以上のものがあります。セキュリティへの影響もあります。作成している「混乱」が「管理者になりすます」であり、危険な操作には管理者アクセスが必要であり、クリーンアップには管理者のなりすましがあると仮定します。 M2では、F への呼び出しには管理者権限があります。 M1ではそうではありません。ユーザーがM2を含むアセンブリにほとんど権限を付与していないが、Nが完全信頼アセンブリにあるとします。 M2のアセンブリ内の潜在的に敵対的なコードは、この誘惑攻撃を通じて管理者のアクセス権を得る可能性があります。

演習として、この攻撃を防ぐためにNをどのように記述しますか?

(もちろん、ランタイムは、M2とNの間に特権を付与または拒否する stack annotations が存在するかどうかを知るのに十分スマートであり、Fを呼び出す前にそれらを元に戻します。しかし、ランタイムは you が作成した他の混乱については知りません。)

ここで重要なことは、例外を処理しているときはいつでも、定義によって何かがひどく間違っており、世界はあなたがそうあるべきだと思っているようなものではないということです。 例外フィルターは、例外条件によって違反された不変式の正確さに依存してはなりません。

更新:

イアン・リングローズは、どうやってこの混乱に巻き込まれたのかと尋ねます。

回答のこの部分は、やや推測的なものになります。ここで説明する設計上の決定の一部は、2012年にマイクロソフトを辞めた後に行われたものです。時間と私は状況の公正な要約を与えることができると思います。

CLRのごく初期に、最終的にブロックする前にフィルターを実行するという設計上の決定が行われました。その設計決定の小さな詳細が必要かどうかを尋ねるのはChris Brummeです。 (更新:悲しいことに、Chrisはもはや質問を受け付けていません。)彼は以前、例外処理モデルの詳細な解説のあるブログを持っていましたが、それがまだインターネット上にあるかどうかはわかりません。

それは合理的な決定です。デバッグの目的で、 before を知りたい。この例外が処理されるかどうか、または完全に未処理の例外が破棄される「未定義の動作」シナリオにあるかどうか、finallyブロックが実行されるプロセス。プログラムがデバッガーで実行されている場合、その未定義の動作には、未処理の例外 before のポイントでのブレークが含まれるので、finallyブロックが実行されます。

これらのセマンティクスがセキュリティと正確性の問題を引き起こすという事実は、CLRチームによって非常によく理解されていました。実際、私は最初の本でそれを議論しました。それは、何年も前に今、そして12年前に私のブログで出荷されました:

https://blogs.msdn.Microsoft.com/ericlippert/2004/09/01/finally-does-not-mean-immediately/

また、CLRチームが望んでいたとしても、セマンティクスを今すぐ「修正」することは重大な変更になるでしょう。

この機能は常にCILとVB.NETに存在し、攻撃者はフィルターを使用してコードの実装言語を制御するため、C#に機能を導入しても新しい攻撃対象領域は追加されません。

そして、セキュリティ問題を導入するこの機能が数十年前から「荒野」にあり、私の知る限り深刻なセキュリティ問題の原因ではなかったという事実は、攻撃者にとってそれほど有益な手段ではないという証拠です。

VB.NETの最初のバージョンの機能がなぜC#になるのに10年以上かかったのですか?そのような「なぜ」の質問に答えるのは難しいですが、この場合、私は十分に簡単にまとめることができます:(1)私たちは私たちの心に非常に多くの他のものを持っていました、そして(2)アンダースは機能が魅力的ではないと思います。 (そして、私はそれにもぞくぞくしていません。)それは何年もの間優先順位リストの一番下にそれを動かしました。

どのようにして、C#6で実装するために優先順位リストで十分に高くしましたか?多くの人々がこの機能を求めましたが、それは常にそれを行うことを支持するポイントです。 VBはすでに持っていて、C#とVBチームは合理的なコストで可能な限り同等にしたいので、それもポイントです。しかし、大きな転換点は:Roslynプロジェクト自体のシナリオがありました例外フィルターは本当に便利だったでしょう(それが何であったか思い出せません;見つけたい場合はソースコードに飛び込みますそれと報告してください!)

言語設計者とコンパイラライタの両方として、 not only コンパイラライタの生活を容易にする機能を優先することに注意してください。ほとんどのC#ユーザーはコンパイラライターではなく、彼らが顧客です!しかし、最終的には、コンパイラーチーム自体を苛立たせるものを含め、この機能が有用な実世界のシナリオのコレクションを持つことでバランスが崩れました。

73
Eric Lippert

しかし、これはcatchブロック内のifステートメントと同じではありませんか?

いいえ、whenなしの2番目のアプローチは、ex.Status== WebExceptionStatus.SendFailureの場合、2番目のCatchに到達しないためです。 whenを使用すると、最初のCatchはスキップされます。

したがって、Statusなしでwhenを処理する唯一の方法は、1つのcatchにロジックを含めることです。

try { … } 
catch (WebException ex) {
   if(ex.Status == WebExceptionStatus.Timeout) {
      //do something
   }
   else if(ex.Status == WebExceptionStatus.SendFailure) {
      //do something
   }
   else
      throw; // see Jeppe's comment 
}
catch (Exception caught) {…}

else throwは、WebExceptionsアプローチと同様に、status=TimeoutまたはSendFailureを含むwhenのみがここで処理されるようにします。他のすべては処理されず、例外が伝播されます。最後のCatchでキャッチされないことに注意してください。したがって、whenとの違いがあります。これは、whenの利点の1つを示しています。

46
Tim Schmelter

これは、catchブロック内のifステートメントと同じではありませんか?

いいえ。それは、例外スローイングシステムの利益のための「差別者」としてより機能します。

例外がスローされる方法を2回覚えていますか?

最初の「throw」(「Studio」が実行する「first-chance」例外)は、ランタイムにnearest例外ハンドラーを見つけるように指示します。例外のこのTypeを処理し、「here」と「there」の間の「最終」ブロックを収集できます。

2番目の「スロー」は、コールスタックを巻き戻し、それらの「最終」ブロックのそれぞれを順番に実行し、実行エンジンを、見つかった例外処理コードのエントリポイントに配信します。

以前は、例外の異なるTypesのみを区別できました。このデコレータは、finer-grainコントロールを提供し、にある可能性がある特定のタイプの例外のみをキャッチします- state何かできること
たとえば(薄気味悪い)接続が切断されたことを示す「データベース例外」を処理し、その場合は自動的に再接続を試みます。
データベース操作の多くは「データベース例外」をスローしますが、あなたは興味があるだけです例外オブジェクトのpropertiesに基づくそれらの特定の「サブタイプ」で、すべてが例外スローシステムで利用可能です。

Catchブロック内の「if」ステートメントは同じ結果を達成しますが、実行時にさらに「コスト」がかかります。このブロックはanyおよびallの「データベース例外」をキャッチするため、allそれらの[ごく一部]に対して有用なことしかできない場合でも。また、できなかった例外を再スローする必要があることを意味します。 、2パス、ハンドラー検索、最終収穫、例外スローファラゴを繰り返します。

類推:[非常に奇妙な]有料橋。

デフォルトでは、料金を支払うためにすべての車を「キャッチ」する必要があります。たとえば、市の従業員が運転する車が通行料からexemptである場合(Ididそれは奇妙だと言ってください)、他の人が運転している車を止めるだけです。

あなたはすべての車を止めて尋ねることができます:

catch( Car car ) 
{ 
   if ( car.needsToPayToll() ) 
      takePayment( car ); 
} 

または、近づいたときに車に「尋問」する方法があれば、ignoreのように、市の従業員が運転している車を次のようにできます。

catch( Car car ) when car.needsToPayToll() 
{ 
   takePayment( car ); 
} 
11
Phill W.

ティムによる答えの拡張。

C#6.0では、新しい機能例外フィルターと新しいキーワードwhenが導入されています。

Try catchブロックの「when」キーワードはifステートメントと同じですか?

Whenキーワードはifのように機能します。 when条件は述語式で、catchブロックに追加できます。述語式が真であると評価された場合、関連するcatchブロックが実行されます。それ以外の場合、catchブロックは無視されます。

C#6.0例外フィルターとキーワードの場合 に素晴らしい説明があります

0