試行/最後に(キャッチなしで)例外をバブルしますか?


115

私は答えがイエスであることをほぼ肯定しています。Try Finalブロックを使用し、Catchブロックを使用しない場合、例外が発生します。正しい?

一般的な実践についての考えはありますか?

セス

回答:


130

はい、そうです。finallyもちろん、ブロックが例外をスローしないことを前提としています。その場合、最初にスローされたブロックが事実上「置き換え」られます。


13
@David:C#のfinallyブロックから戻ることはできません。
Jon Skeet、2010

3
msdnのドキュメントもこの回答を確認しています。または、呼び出しスタックの上位にあるtry-finallyステートメントのtryブロックでスローされる可能性のある例外をキャッチすることもできます。つまり、try-finallyステートメントを含むメソッドを呼び出すメソッド、そのメソッドを呼び出すメソッド、または呼び出しスタック内の任意のメソッドで例外をキャッチできます。例外がキャッチされない場合、finallyブロックの実行は、オペレーティングシステムが例外の巻き戻し操作をトリガーするかどうかによって異なります。
ブロードバンド

60

一般的な実践についての考えはありますか?

はい。気をつけて。最終的にブロックが実行されている場合、未処理の予期しない例外がスローされているため実行されている可能性があります。つまり、何かが壊れており、まったく予期しないことが起こっている可能性があります。

そのような状況では、間違いなく、finallyブロックでコードを実行するべきではありません。finallyブロックのコードは、それが依存しているサブシステムが健全であると想定して構築されている可能性があります。finallyブロックのコードは事態を悪化させる可能性があります。

たとえば、次のようなことがよくあります。

DisableAccessToTheResource();
try
{
    DoSomethingToTheResource();
}
finally
{
    EnableAccessToTheResource();
}

このコードの作成者は、「私は一時的に世界の状態を変化させています。呼び出される前の状態に戻す必要があります」と考えています。しかし、これがうまくいかない可能性があるすべての方法について考えてみましょう。

まず、リソースへのアクセスが呼び出し元によって既に無効にされている可能性があります。その場合、このコードは、おそらく時期尚早にそれを再度有効にします。

次に、DoSomethingToTheResourceが例外をスローする場合、リソースへのアクセスを有効にするために正しいことは何ですか?リソースを管理するコードが予期せず壊れています。このコードは、事実上、「管理コードが壊れている場合は、他のコードがその壊れたコードをできるだけ早く呼び出して、恐ろしく失敗する可能性があることを確認してください」と述べています。これは悪い考えのようです。

3番目に、DoSomethingToTheResourceが例外をスローする場合、EnableAccessToTheResourceも例外をスローしないことをどのようにして知ることができますか?リソースの使用がどれほどひどい場合でも、クリーンアップコードに影響する可能性があります。その場合、元の例外が失われ、問題の診断が難しくなります。

私はtry-finallyブロックを使用せずに次のようなコードを書く傾向があります。

bool wasDisabled = IsAccessDisabled();
if (!wasDisabled)
    DisableAccessToTheResource();
DoSomethingToTheResource();
if (!wasDisabled)
    EnableAccessToTheResource();

これで、必要がない限り、状態は変化しません。これで、呼び出し元の状態が乱れることはありません。そして今、DoSomethingToTheResourceが失敗した場合、アクセスを再度有効にしません。私たちは、何かが深く壊れていて、コードの実行を続けようとすることで状況を悪化させるリスクがないと想定しています。可能であれば、発信者に問題を処理してもらいます。

では、finallyブロックを実行するのはいつよいのでしょうか。まず、例外が予想される場合。たとえば、他の誰かがファイルをロックしているため、ファイルをロックしようとすると失敗する場合があります。その場合、例外をキャッチしてユーザーに報告することは理にかなっています。その場合、何が壊れているかについての不確実性が減少します。片付けによって事態が悪化することはほとんどありません。

次に、クリーンアップするリソースが不足しているシステムリソースである場合。たとえば、finallyブロックでファイルハンドルを閉じることは理にかなっています。(もちろん、「使用」はtry-finallyブロックを書き込む別の方法にすぎません。)ファイルの内容が破損している可能性がありますが、今はそれに対してできることは何もありません。ファイルハンドルは最終的に閉じられるため、遅くなるよりも早くなる可能性があります。


7
エラー処理に関しては、まだ業界としてまだ行動を起こしていない例が増えています。例外以外に提案できることは何もありませんが、適切な行動方針が合理的に容易に実行される可能性が高まることを将来に期待しています。
Jon Skeet

vb.netでは、Finallyブロックのコードで、保留中の例外を知ることができます。例外階層を設定して「実行しなかった。そうでなければ状態は大丈夫」と「状態が壊れている」を区別する場合、保留中の例外が悪いものでない場合にのみ、Finallyブロックを実行できます。私はディスポーザ/クリーンアップルーチンに例外をキャッチさせ、DisposerFailedException(「悪い」カテゴリにあり、InnerExceptionとして元の例外が含まれています)をスローさせたいです。それを容易にするために、Dispose(Ex as Exception)を備えた標準のiDisposableExインターフェースを見たいのですが。
スーパーキャット

オブジェクトの状態が悪いときに例外が発生し、クリーンアップを試行すると事態が悪化する場合があることを理解していますが、そのような問題は、おそらくデリゲートの助けを借りて、クリーンアップコードで処理する必要があると思います。たとえば、ロックラッパーは、 "CheckIfSafeToUnlock(Ex as Exception)"関数デリゲートを公開する可能性があります。これは、ロックされたオブジェクトが無効な状態にあるときに設定され、それ以外の場合はクリアされます。ロックを解放する前に、ラッパーはデリゲートをチェックできます。空でない場合は、それを実行して、trueが返された場合にのみロックを解放できます。
スーパーキャット

ラッパーにこのようなデリゲートを使用させると、オブジェクトの状態を操作したコードが、中断された場合に適切なクリーンアップアクションを選択できるようになります。このようなアクションには、データ構造を修復してTrueを返す、元の例外をラップする別の「より深刻な」例外をスローする、Falseを返す間に元の例外を浸透させるなどがあります。
スーパーキャット

2
エリック、すばらしい答えをありがとう。あなたとジョンの両方が正しいので、私は2つの質問をするべきだったと思います。いずれにせよ、あなたは賛成です。質問にご注意いただきありがとうございます。
Seth Spearman
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.