なぜ{…}やっと{…}が良いのでしょうか。{…}キャッチ{}してみませんか?


201

引数なしでキャッチを使用することは、特にそのキャッチが何もしない場合は、悪い形だと人々が言うのを見てきました。

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
catch   // No args, so it will catch any exception
{}
reader.Close();

ただし、これは適切な形式と見なされます。

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
finally   // Will execute despite any exception
{
  reader.Close();
}

私が知る限りでは、クリーンアップコードをfinallyブロックに配置することと、try..catchブロックの後にクリーンアップコードを配置することの唯一の違いは、tryブロックにreturnステートメントがある場合です(その場合、finallyのクリーンアップコードは実行されますが、try..catchの後のコードは実行されません)。

そうでなければ、最後に何がそれほど特別なのですか?


7
あなたが扱えないトラを捕まえる前に、あなたはあなたの最後の願いを文書化すべきです。

ドキュメントの例外トピックは、いくつかの良い洞察を与えるかもしれません。また、Finally Blockの例もご覧ください。
Athafoud 2016

回答:


357

大きな違いはtry...catch、例外が飲み込まれ、エラーが発生したという事実を隠すことです。try..finallyクリーンアップコードを実行すると、例外が続行され、処理方法を知っているものによって処理されます。


11
カプセル化を念頭に置いて記述されたコードは、例外が発生した時点でのみ例外を処理できる可能性があります。それをコールスタックに渡すだけで、他の何かが任意の例外を処理できるようになるという絶望的な希望は、災害のレシピです。
デビッドアルノ

3
ほとんどの場合、クラスライブラリレベルではなく、アプリケーションレベル(たとえば、特定の構成設定)から特定の例外が発生する理由は明らかです。
Mark Cidade、

88
David-私はプログラムをすばやく失敗させて、プログラムを不明な状態で実行したままにせずに問題を認識できるようにしたいと思います。
エリックフォーブス

6
例外の後でプログラムが不明な状態になっている場合は、コードが間違っています。
Zan Lynx

41
@DavidArno、カプセル化を念頭に置いて記述されたコードは、スコープ内でのみ例外を処理する必要があります。他の誰かが処理するために、他のものは渡されるべきです。ユーザーからファイル名を取得してファイルを読み取るアプリケーションがあり、ファイルリーダーがファイルを開くときに例外を受け取った場合、アプリケーションは次のように渡す(または例外を消費して新しい例外をスローする)必要があります。ねえ-ファイルが開かなかったので、ユーザーに別のファイルを要求します。ファイルリーダーは、ユーザーにプロンプ​​トを表示したり、応答として他のアクションを実行したりできません。その唯一の目的は、ファイルを読み取ることです。
iheanyi 2014年

62

「最後に」は「プログラムの状態が正常であることを確認するために常にしなければならないこと」の記述です。そのため、例外によってプログラムの状態がスローされる可能性がある場合は、常に適切な形式を使用してください。コンパイラーは、Finallyコードが確実に実行されるように、最大​​限の努力をしています。

「キャッチ」は「この例外から回復できる」という声明です。本当に修正できる例外からのみ回復する必要があります。引数なしでcatchを実行すると、「ねえ、何からでも回復できます!」と言われます。

すべての例外から回復することが可能である場合、意図を宣言していることについて、それは実際には意味論的な問題です。ただし、そうではありません。ほとんどの場合、あなたのフレームより上のフレームは、特定の例外を処理するために備えられています。そのため、最後に使用して、クリーンアップコードを無料で実行しますが、より知識のあるハンドラーに問題を処理させます。


1
あなたの感情は広まっていますが、残念ながら、もう1つの重要なケースは無視されます。不変条件が保持されなくなる可能性のあるオブジェクトを明示的に無効にします。一般的なパターンは、コードがロックを取得し、オブジェクトに変更を加え、ロックを解放することです。すべてではなく一部の変更を行った後で例外が発生した場合、オブジェクトは無効な状態のままになる可能性があります。IMHOのより良い代替手段存在する必要がありますが、オブジェクトの状態が無効である可能性があるときに発生する例外をキャッチし、明示的に状態を無効にしてから再スローするよりも良い方法はありません。
スーパーキャット2013年

32

その1つの行が例外をスローするとき、あなたはそれを知らないでしょう。

コードの最初のブロックでは、例外は単純に吸収され、プログラムの状態が間違っている場合でもプログラムは実行を継続します。

2番目のブロックでは、例外がスローされてバブルアップしますreader.Close()実行は保証されています。

例外が予期されない場合は、try..catchブロックをそのままにしないでください。プログラムが不良状態になったときにデバッグするのが難しくなり、その理由がわかりません。


21

最後に、何があっても実行されます。したがって、tryブロックが成功した場合は実行され、tryブロックが失敗した場合は、catchブロックを実行してから、finallyブロックを実行します。

また、次の構文を使用することをお勧めします。

using (StreamReader reader=new  StreamReader("myfile.txt"))
{
}

usingステートメントは自動的にtry / finallyでラップされるため、ストリームは自動的に閉じられます。(実際に例外をキャッチしたい場合は、usingステートメントの周りにtry / catchを配置する必要があります)。


5
これは正しくありません。を使用しても、コードはtry / catchでラップされず、try / finallyと表示されるはずです
pr0nin

8

次の2つのコードブロックは同等ですが、同じではありません。

try
{
  int i = 1/0; 
}
catch
{
  reader.Close();
  throw;
}

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. 「最終的に」は意図を明らかにするコードです。コンパイラーや他のプログラマーに、このコードを実行する必要があることを宣言します。
  2. 複数のcatchブロックがあり、クリーンアップコードがある場合、最後に必要になります。最終的には、各catchブロックでクリーンアップコードを複製することになります。(乾燥原理)

最後にブロックは特別です。CLRは、finallyブロックを含むコードをキャッチブロックとは別に認識し、処理します。CLRは、finallyブロックが常に実行されることを保証するために、非常に長くなります。それはコンパイラーからの単なる構文糖質ではありません。


5

ここでのコンセンサスのように見えることに同意します。空の「キャッチ」は、tryブロックで発生した可能性のあるすべての例外をマスクするため、不適切です。

また、読みやすさの観点から、「try」ブロックが表示されると、対応する「catch」ステートメントがあると思います。リソースが「finally」ブロックで割り当て解除されるようにするために「try」のみを使用している場合は、代わりに「using」ステートメントを検討することができます。

using (StreamReader reader = new StreamReader('myfile.txt'))
{
    // do stuff here
} // reader.dispose() is called automatically

IDsposableを実装する任意のオブジェクトで「using」ステートメントを使用できます。オブジェクトのdispose()メソッドは、ブロックの最後で自動的に呼び出されます。


4

Try..Catch..Finallyメソッドがローカルで例外を処理する方法を知っている場合は、を使用します。例外はTryで発生し、Catchで処理され、その後、最終的にクリーンアップが行われます。

メソッドが例外の処理方法を認識していないが、発生後にクリーンアップが必要な場合 Try..Finally

これにより、例外は呼び出しメソッドに伝播され、呼び出しメソッドに適切なCatchステートメントがある場合は処理されます。現在のメソッドまたは呼び出しメソッドのいずれかに例外ハンドラがない場合、アプリケーションがクラッシュします。

これによりTry..Finally、呼び出し側のメソッドに例外を伝播する前に、ローカルのクリーンアップが確実に行われます。


1
この回答と同じくらい基本的なものですが、これは絶対に最高の回答です。後者の2つのうち1つが空のままであっても、try / catch / finallyの習慣を身に付けるのは良いことです。catchブロックが存在し、空になる可能性は非常にまれですが、少なくともtry / catch / finallyを常に記述している場合は、コードを調べているときに空のブロックが表示されます。同様に、finallyブロックを空にすることも役立ちます。後でクリーンアップが必要な場合、または例外発生時の状態をデバッグする必要がある場合は、非常に役立ちます。
ジェシーウィリアムズ

3

try..finallyブロックは、発生した例外をスローします。すべてfinallyませんが、例外がスローされる前に、クリーンアップコードが実行されていることを確認しています。

空のcatchを指定したtry..catchは、すべての例外を完全に消費し、それが発生したという事実を隠します。リーダーは閉じられますが、正しいことが起こったかどうかはわかりません。ファイルにiを書き込むことが目的である場合はどうなりますか?この場合、コードのその部分には到達せず、myfile.txtは空になります。すべてのダウンストリームメソッドがこれを適切に処理しますか?空のファイルが表示された場合、例外がスローされたため、空であると正しく推測できますか?例外をスローし、何かが間違っていることを知らせた方がよいでしょう。

別の理由は、このように行われたtry..catchが完全に正しくないことです。そうすることであなたが言っていることは、「何が起こっても私はそれを処理できる」ということです。についてStackOverflowExceptionはどうですか、その後クリーンアップできますか?どうOutOfMemoryExceptionですか?一般に、予期し、処理方法を知っている例外のみを処理する必要があります。


2

キャッチする例外の種類やそれをどう処理するかわからない場合は、catchステートメントを使用しても意味がありません。何をすべきかを知るために、状況についてより多くの情報を持っているかもしれない上位の呼び出し元にそれを残しておくべきです。

例外が発生した場合に備えて、finallyステートメントがまだあるはずです。これにより、例外が呼び出し元にスローされる前にリソースをクリーンアップできます。


2

読みやすさの観点からは、将来のコードリーダーに「ここにあるものは重要であり、何が起こっても実行する必要がある」と明示的に伝えています。これはいい。

また、空のcatchステートメントは、特定の「匂い」を持つ傾向があります。それらは、発生する可能性のあるさまざまな例外とそれらの処理方法について開発者が考えていない兆候である可能性があります。


2

最後にオプションです-クリーンアップするリソースがない場合、「最終的に」ブロックする理由はありません。


2

取得元:ここ

メソッドの正常な実行の一部として、例外の発生とキャッチが日常的に発生してはなりません。クラスライブラリを開発する場合、例外が発生する可能性のある操作を実行する前に、クライアントコードにエラー条件をテストする機会を与える必要があります。たとえば、System.IO.FileStreamは、Readメソッドを呼び出す前にチェックできるCanReadプロパティを提供し、次のコードスニペットに示すように、潜在的な例外の発生を防ぎます。

Dim str As Stream = GetStream()If(str.CanRead)Then 'コードでストリームを読み取るEnd If

例外を発生させる可能性のある特定のメソッドを呼び出す前にオブジェクトの状態をチェックするかどうかの決定は、オブジェクトの予期される状態によって異なります。存在するはずのファイルパスと、読み取りモードでファイルを返す必要があるコンストラクタを使用してFileStreamオブジェクトを作成する場合、CanReadプロパティをチェックする必要はありません。FileStreamを読み取ることができない場合、行われたメソッド呼び出しの予想される動作に違反し、例外が発生します。対照的に、メソッドが読み取り可能またはそうでない可能性があるFileStream参照を返すものとして文書化されている場合は、データの読み取りを試みる前にCanReadプロパティを確認することをお勧めします。

「例外まで実行」コーディング手法を使用すると発生する可能性があるパフォーマンスへの影響を説明するために、キャストが失敗した場合にInvalidCastExceptionをスローするキャストのパフォーマンスを、キャストが失敗した場合にnullを返すC#as演算子と比較します。2つの手法のパフォーマンスは、キャストが有効な場合(テスト8.05を参照)と同じですが、キャストが無効であり、キャストを使用すると例外が発生する場合、キャストを使用すると600倍遅くなります。演算子として(テスト8.06を参照)。例外をスローする手法のパフォーマンスへの影響には、例外の割り当て、スロー、およびキャッチのコストと、例外オブジェクトのその後のガベージコレクションのコストが含まれます。つまり、例外をスローすることの瞬間的な影響はそれほど高くありません。より多くの例外がスローされると、


2
スコット-上で引用したテキストがexpertsexchange.comのペイウォールの背後にある場合、おそらくここに投稿しないでください。私はこれについて間違っているかもしれませんが、それは良い考えではないと思います。
Onorio Catenacci


2

プログラマー向けのC#を読むと、finallyブロックはアプリケーションを最適化し、メモリリークを防ぐように設計されていることがわかります。

CLRはリークを完全に排除していません...プログラムが不注意で不要なオブジェクトへの参照を保持していると、メモリリークが発生する可能性があります

たとえば、ファイルまたはデータベース接続を開くと、マシンはそのトランザクションに対応するためにメモリを割り当て、そのメモリは、disposedまたはcloseコマンドが実行されない限り保持されません。ただし、トランザクション中にエラーが発生した場合、コマンドはtry.. finally..ブロック内になければ終了しません。

catchfinallycatchは、エラーを自分で処理/管理または解釈する方法を提供するための設計であるという意味で異なりました。「悪い奴らを捕まえた、彼らに何をしてほしいのか」と言う人だと思ってください。一方finally、リソースが適切に配置されていることを確認するように設計されています。誰かが悪人がいてもいなくても、あなたの資産がまだ安全であることを確認するだろうという人のことを考えてください。

そして、あなたはそれら2つが永久に一緒に働くことを許すべきです。

例えば:

try
{
  StreamReader reader=new  StreamReader("myfile.txt");
  //do other stuff
}
catch(Exception ex){
 // Create log, or show notification
 generic.Createlog("Error", ex.message);
}
finally   // Will execute despite any exception
{
  reader.Close();
}

1

最後に、catchステートメントが呼び出し元のプログラムに例外をスローした場合でも、リソースをクリーンアップできます。空のcatchステートメントを含む例では、ほとんど違いはありません。ただし、catchの場合、何らかの処理を行ってエラーをスローするか、catchがまったくない場合でも、finallyは実行されます。


1

1つには、処理する必要のない例外をキャッチすることは悪い習慣です。チェックアウトネットのパフォーマンスについては、第5章をから.NETアプリケーションのパフォーマンスとスケーラビリティを向上させます。補足として、tryブロック内にストリームをロードしているはずです。そうすれば、失敗した場合に適切な例外をキャッチできます。tryブロックの外でストリームを作成すると、その目的が損なわれます。


0

おそらく多くの理由の中で、例外の実行は非常に遅いです。これが頻繁に発生すると、実行時間を簡単に無効にすることができます。


0

すべての例外をキャッチするtry / catchブロックの問題は、不明な例外が発生した場合にプログラムが不確定な状態になることです。これは完全にフェイルファストルールに反します。例外が発生した場合にプログラムを続行させたくない場合です。上記のtry / catchはOutOfMemoryExceptionsさえキャッチしますが、それは間違いなくプログラムが実行されない状態です。

try / finallyブロックを使用すると、高速で失敗しながらコードのクリーンアップを実行できます。ほとんどの状況では、すべての例外をグローバルレベルでキャッチするだけで、例外をログに記録して終了できます。


0

例外がスローされない限り、サンプル間の効果的な違いは無視できます。

ただし、「try」句の中に例外がスローされた場合、最初の例ではそれを完全に飲み込みます。2番目の例では、コールスタックの次のステップに例外が発生します。したがって、上記の例の違いは、一方が例外を完全に覆い隠し(最初の例)、もう一方(2番目の例)は後で処理できるように例外情報を保持することです。 'finally'句のコンテンツをまだ実行しています。

たとえば、最初の例の「catch」節に例外をスローしたコード(最初に発生したものまたは新しい例外)を配置した場合、リーダーのクリーンアップコードは実行されません。最後に、「catch」節で何が発生するかに関係なく実行されます。

したがって、「catch」と「finally」の主な違いは、「finally」ブロックのコンテンツ(いくつかのまれな例外があります)は、予期しない例外が発生した場合でも、実行が保証されていると見なすことができることです。 'catch'句(ただし 'finally'句の外)は、このような保証を持ちません。

ちなみに、StreamとStreamReaderはどちらもIDisposableを実装しており、「using」ブロックにラップできます。'using'ブロックは、try / finally( 'catch'なし)と意味的に同等であるため、例をより簡潔に次のように表現できます。

using (StreamReader reader = new  StreamReader("myfile.txt"))
{
  int i = 5 / 0;
}

... StreamReaderインスタンスがスコープ外になると、インスタンスを閉じて破棄します。お役に立てれば。


0

{…} catch {}を試すのは必ずしも悪いことではありません。これは一般的なパターンではありませんが、スレッドの終わりに(おそらく)開いているソケットを閉じるなど、リソースをシャットダウンする必要がある場合に使用する傾向があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.