C#で例外をキャッチして再スローする理由


557

記事C#-シリアル化可能なDTO上のデータ転送オブジェクトを見ています。

記事には次のコードが含まれています。

public static string SerializeDTO(DTO dto) {
    try {
        XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
        StringWriter sWriter = new StringWriter();
        xmlSer.Serialize(sWriter, dto);
        return sWriter.ToString();
    }
    catch(Exception ex) {
        throw ex;
    }
}

記事の残りの部分は正気で妥当なように見えますが(noobにとって)、そのtry-catch-throwはWtfExceptionをスローします... これは例外をまったく処理しないこととまったく同じではありませんか?

エルゴ:

public static string SerializeDTO(DTO dto) {
    XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
    StringWriter sWriter = new StringWriter();
    xmlSer.Serialize(sWriter, dto);
    return sWriter.ToString();
}

または、C#でのエラー処理に関する基本的な何かが欠けていますか?Java(チェック済みの例外を除く)とほとんど同じですよね。...つまり、どちらもC ++を洗練しました。

スタックオーバーフローの質問パラメーターなしのキャッチを再スローすることと何もしないことの違いは何ですか?try-catch-throwは何もしないという私の主張をサポートしているようです。


編集:

将来このスレッドを見つけた人のために要約すると...

しない

try {
    // Do stuff that might throw an exception
}
catch (Exception e) {
    throw e; // This destroys the strack trace information!
}

スタックトレース情報は、問題の根本的な原因を特定する上で非常に重要です。

行う

try {
    // Do stuff that might throw an exception
}
catch (SqlException e) {
    // Log it
    if (e.ErrorCode != NO_ROW_ERROR) { // filter out NoDataFound.
        // Do special cleanup, like maybe closing the "dirty" database connection.
        throw; // This preserves the stack trace
    }
}
catch (IOException e) {
    // Log it
    throw;
}
catch (Exception e) {
    // Log it
    throw new DAOException("Excrement occurred", e); // wrapped & chained exceptions (just like java).
}
finally {
    // Normal clean goes here (like closing open files).
}

(Javaのように)より具体的でない例外の前に、より具体的な例外をキャッチします。


参照:


8
良い要約; finallyブロックを含めるための追加ポイント。
FredrikMörk、

「スロー」を使用できることを付け加えておきます。「throw」の前にe.Dataコレクションのメソッドに送信されたパラメーターを追加することで、さらに役立つ。ステートメント
Michael Bahig 2013

@MickTheWarMachineDesigner(および非常勤のペインター)。えっ?あなたはMicroshite Suckwell(おそらく2005年以降、私が知っていることすべて)の例外を処理することを話している。例外処理全般について話していました。そして、はい、この4年前に投稿して以来、いくつかのことを学びました...しかし、はい、あなたは正当なポイントを持っていると告白しますが、実際のポイントを逃したと思います。あなたが私のドリフトを手に入れたら?この質問は、C#での一般化された例外処理についてです。より具体的には、あらゆる種類の例外の再スローについて... 涼しい?
corlettk 2013

質問の編集の概要セクションを独自の回答に移動することを検討してください。理由については、質問からの自己回答の編集および質問に埋め込まれた回答をご覧ください。
DavidRR 2016

2
「排泄が起こった」部分に気付かなかった人はいますか?コードがうんざりしたようです!
Jason Loki Smith

回答:


430

最初; 記事のコードが行う方法は悪です。throw ex例外のコールスタックを、このthrowステートメントがあるポイントにリセットします。例外が実際に作成された場所に関する情報を失う。

次に、そのようにキャッチして再スローしただけの場合、付加価値はありません。上記のコード例throw exは、try-catchがなくても同じように(または、ビットを考えるとさらに優れています)です。

ただし、例外をキャッチして再スローする必要がある場合があります。ロギングはそれらの1つである可能性があります。

try 
{
    // code that may throw exceptions    
}
catch(Exception ex) 
{
    // add error logging here
    throw;
}

6
@Fredrick、そのexオブジェクトを使用しない場合はfyi(おそらくご存知かもしれません)をインスタンス化する必要がない場合は、インスタンス化する必要はありません。
エオインキャンベル

78
@Eoin:インスタンス化されていない場合、ログに記録するのはかなり困難です。
サムアックス

30
ええ、私は「悪」が正しいと思います...コードの大きな本体からどこかにスローされたnullポインタ例外の場合を考えてください。メッセージはバニラで、スタックトレースがないと、「どこかにnullがありました」というメッセージが残ります。生産が停止している場合は良くありません。フラミンの問題を解決するのに数分もかかりません。それを却下または修正します...優れた例外処理は、金の重さに見合う価値があります。
corlettk 2009年

4
それはJavaにも当てはまりますか?「スロー」対「スローex」?
JasonStoltz、2011年

8
@ジェイソン、この質問を見てください。Javaではthrow ex、スタックトレースを再起動しません。
Matthew Flaschen、2012年

117

これを行わないでください。

try 
{
...
}
catch(Exception ex)
{
   throw ex;
}

スタックトレース情報が失われます...

どちらか、

try { ... }
catch { throw; }

または

try { ... }
catch (Exception ex)
{
    throw new Exception("My Custom Error Message", ex);
}

再スローしたくなる理由の1つは、たとえば次のように、さまざまな例外を処理している場合です。

try
{
   ...
}
catch(SQLException sex)
{
   //Do Custom Logging 
   //Don't throw exception - swallow it here
}
catch(OtherException oex)
{
   //Do something else
   throw new WrappedException("Other Exception occured");
}
catch
{
   System.Diagnostics.Debug.WriteLine("Eeep! an error, not to worry, will be handled higher up the call stack");
   throw; //Chuck everything else back up the stack
}

7
単にcatch {throw}を完全に除外しないのはなぜですか?
AnthonyWJones、2009年

3
catch {throw;を残すことにはまだ価値があります。特定の例外タイプのcatchのリストの最後にある理由から、コメントで十分かもしれませんが、それは著者がケースを検討したことを証明しています。コードをいつ読むかを推測しないのは良いことです。
アナカタ2009年

87
なんらかの理由で、SQLExceptionの名前が気になります。
マイケルマイヤーズ

13
そのキャッチ(Exception){throw new Exception(...)}は、例外情報を難読化し、例外のフィルタリングをコールスタックまでさらに不必要に難しくしているという理由だけで、決して、絶対に行うべきではありません。あるタイプの例外をキャッチして別のタイプの例外をスローする必要があるのは、抽象化レイヤーを実装していて、プロバイダー固有の例外タイプ(たとえば、SqlExceptionとXmlException)をより一般的なタイプ(たとえば、DataLoadingException)に変換する必要があるときだけです。
jammycakes

3
@dark_perfectでは、メソッドの最初でその引数を前もって確認し、そこにArgumentNullExceptionをスローする必要があります(高速に失敗します)。
Andrei Bozantan 2014年

56

C#(C#6より前)は、CILの「フィルターされた例外」をサポートしていません。VBがサポートしているため、C#1-5で例外を再スローする理由の1つは、catch()の時点で十分な情報がないことです。実際に例外をキャッチしたいかどうかを判断します。

たとえば、VBでは次のことができます

Try
 ..
Catch Ex As MyException When Ex.ErrorCode = 123
 .. 
End Try

...これは、異なるErrorCode値を持つMyExceptionsを処理しません。v6より前のC#では、ErrorCodeが123でない場合、MyExceptionをキャッチして再度スローする必要がありました。

try 
{
   ...
}
catch(MyException ex)
{
    if (ex.ErrorCode != 123) throw;
    ...
}

C#6.0以降では、VBと同じようにフィルタリングできます

try 
{
  // Do stuff
} 
catch (Exception e) when (e.ErrorCode == 123456) // filter
{
  // Handle, other exceptions will be left alone and bubble up
}

2
Dave、しかし(少なくともJavaでは)「一般的な」MyExceptionをスローしません。SPECIFIC例外タイプを定義してスローし、catchブロックでタイプごとに区別できるようにします...しかし、はい、あなたが例外のアーキテクトでない場合(私はここでJDBCのSQLException(Java)を考えています)これはうんざりするほど一般的であり、getErrorCode()メソッドを公開しています...うーん...あなたはポイントを持っている、それはそれだけです私は可能であれば、それを行うには良い方法があると思う乾杯メイト私はあなたの時間、多くのことを感謝キース。。。。
corlettk

1
さて、質問は「なぜC#で例外をキャッチして再スローするのか?」であり、これが答えです。=] ...そして特殊な例外があっても、例外フィルターは理にかなっています。たとえば、SqlTimeoutExceptionとSqlConnectionResetException(どちらもSqlException)を処理している場合を考えてください。例外フィルターを使用すると、これら2つのうちの1つである場合にのみSqlExceptionをキャッチできます。そのため、これら2つを同じように処理してtry / catchを乱雑にする代わりに、「exがSqlTimeoutExceptionまたはexがSqlConnectionResetExceptionであるときにexをキャッチする」ことができます。(私はデイブではありません)
bzlm 2009年

3
フィルターされた例外はC#6で提供されます!
クリスパロット卿2015

14

次のようなコードを持つ私の主な理由:

try
{
    //Some code
}
catch (Exception e)
{
    throw;
}

キャッチにブレークポイントを設定できるようにするため、インスタンス化された例外オブジェクトがあります。開発/デバッグ中にこれをよく行います。もちろん、コンパイラーはすべての未使用のeについて警告を出します。理想的には、リリースビルドの前に削除する必要があります。

ただし、デバッグ中は便利です。


1
ええ、私はその1を支払いますが、はい、あなたがでていることを確認したいとは思わないでしょう公表コード...エルゴ:私は;-)それを公開する恥ずかしいだろう
corlettk

25
実際、これは必須ではありません。VisualStudioでは、例外がスローされたときにデバッガーが中断するように設定でき、インスペクターウィンドウに例外の詳細が表示されます。
jammycakes 2009年

8
デバッグ中にのみコードを使用したい場合は、#if DEBUG ... #endifを使用します。これらの行を削除する必要はありません
Michael Freidgeim

1
ええ、私はそれを数回自分でやった。時々、リリースに脱出するでしょう。@jammycakes Visual Studioのブレークオン例外の問題は、スローされる例外が1つだけではない(またはそのタイプの1つでさえない)場合があることです。それでも、「例外によってスキップされた場合にブレークする」というブレークポイント条件については知りません。それまでは、これは有用なままです。マイケルFreidgeimは:#if DEBUG周りBOTH try {} catch () {...}私はムカムカ...前のプロセッサは、一般的に私の友人ではなく、話している行い、率直に言って、ビット厄介です。

11

例外を再スローする正当な理由としては、例外に情報を追加したり、独自の作成で元の例外をラップしたりすることが考えられます。

public static string SerializeDTO(DTO dto) {
  try {
      XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
      StringWriter sWriter = new StringWriter();
      xmlSer.Serialize(sWriter, dto);
      return sWriter.ToString();
  }
  catch(Exception ex) {
    string message = 
      String.Format("Something went wrong serializing DTO {0}", DTO);
    throw new MyLibraryException(message, ex);
  }
}

ありがとう、うん、例外のラッピング(特にチェーン)は完全に正常です...正常でないものは例外をキャッチしているので、スタックトレースを削除したり、さらに悪いことに、それを食べたりすることができます。
corlettk 2009年

10

これは例外をまったく処理しないこととまったく同じではありませんか?

厳密には同じではありません。例外のスタックトレースをリセットします。これはおそらく間違いであり、したがって悪いコードの例であることに私は同意します。


8

あなたはexを投げたくない-これはコールスタックを失うからだ。例外処理(MSDN)を参照してください。

そして、はい、try ... catchは何の役にも立ちません(コールスタックが失われることを除いて、実際にはもっと悪いです-何らかの理由でこの情報を公開したくない場合を除きます)。


throw exを使用しても、コールスタック全体が失われるのではなく、例外が発生したポイントから、コールスタックの一部が失われるだけです。ただし、例外をスローしたメソッドからの呼び出しスタックを、クライアントが呼び出した場所まで保持します。実際にそれを使用する場合もあれば、Microsoftの優秀な人々がそれを許可しなかった場合もあります。そうは言っても、私はそれを使ったことはありません。覚えておくべきもう1つの問題は、例外をスローすることは負荷が高いことです。非常に正当な理由がある場合にのみ実行してください。正当化できると思うロギングなど
Charles Owen

5

人々が言及しなかった点は、.NET言語は実際には適切な区別をしていませんが、例外が発生したときにアクションを実行する必要あるかどうか、およびそれを解決するかどうかは、実際には異なる質問です。解決する見込みのない例外に基づいてアクションを実行する必要がある多くの場合があり、例外を「解決」するために必要なのは、スタックを特定のポイントまで巻き戻すことだけである場合があります。それ以上のアクションは必要ありません。 。

「処理」できるものだけを「キャッチ」する必要があるという一般的な知識があるため、例外が発生したときにアクションを実行する必要がある多くのコードはそうではありません。たとえば、多くのコードはロックを取得し、保護されたオブジェクトを「一時的に」その不変条件に違反する状態にしてから、オブジェクトを正当な状態にしてから、他の人がオブジェクトを見る前にロックを解除します。オブジェクトが危険なほど無効な状態にあるときに例外が発生した場合、一般的には、オブジェクトがその状態のままでロックを解除します。はるかに良いパターンは、オブジェクトが「危険な」状態にあるときに発生する例外を明示的にロックを無効にすることです。これにより、それを取得しようとすると、すぐに失敗します。

ほとんどの.NET言語では、コードが例外に基づいてアクションを実行する唯一の方法は、catchそれを(例外を解決しないことがわかっている場合でも)コードに対して実行し、問題のアクションを実行してから再実行することthrowです。コードがスローされた例外を気にしない場合に考えられる別のアプローチはoktry/finallyブロックでフラグを使用することです。セットokにフラグをfalseブロックする前に、とにtrueブロックが終了する前に、および任意の前にreturnブロック内だという。次に、内でfinallyokが設定されていない場合は、例外が発生したと想定します。このようなアプローチは、意味的にはcatch/ よりも優れていますが、throw見苦しく、メンテナンスが容易ではありません。


5

これは、ライブラリまたはDLLのプログラミング関数の場合に役立ちます。

この再スロー構造を使用して、意図的にコールスタックをリセットし、関数内の個々の関数からスローされた例外を確認する代わりに、関数自体から例外を取得できます。

これは、スローされた例外がよりクリーンになり、ライブラリの「ルーツ」に入らないようにするために使用されていると思います。


3

キャッチスローの1つの考えられる理由は、スタックのより深いところで例外フィルターを無効にすることです(ランダムな古いリンク)。しかし、もちろん、それが意図であった場合、そこにそのように言うコメントがあります。


リンクを読むまで、どこに何があるのか​​わかりませんでした...そして、あなたが何をしているのかはまだよくわかりません...私はVB.NETにまったく慣れていません。その結果、合計が「不整合」と報告されると思いますよね?...私は静的メソッドの大ファンです..それらが単純であることを除けば、コードから属性の設定を分離すると、不整合の可能性が少なくなります実際の作業を行います。スタックは「セルフクレンジング」です。
corlettk 2008

3
「try {Foo();}最後に{Bar();}」と書くと、FooとBarの間で何も実行されないことが期待されます。しかし、これは真実ではありません。呼び出し元が例外フィルターを追加し、間に「キャッチ」がなく、Foo()がスローした場合、呼び出し元からの他のランダムコードが、最終的に(バー)が実行される前に実行されます。不変条件または高度なセキュリティを破った場合、これは非常に悪いことです。最終的にはそれらが「即座に」正常に復元され、他のコードが一時的な変更を認識しないことを期待します。
ブライアン

3

これは、catchブロックで何をしているのか、および呼び出し元のコードにエラーを渡したいかどうかによって異なります。

言ってCatch io.FileNotFoundExeption exから、別のファイルパスなどを使用しても、エラーが発生する場合があります。

またやってThrowの代わりに、Throw Exあなたは完全なスタックトレースを維持することができます。Throw exは、throwステートメントからスタックトレースを再開します(それが理にかなっていると思います)。


3

他の多くの回答は、例外を再スローする必要がある理由の良い例を提供していますが、「最後の」シナリオについて言及している人はいません。

この例としては、カーソルを(たとえば、待機カーソルに)設定するメソッドがあり、そのメソッドにいくつかの終了点(if()return;など)があり、カーソルがメソッドの終わり。

これを行うには、すべてのコードをtry / catch / finallyでラップします。最終的に、カーソルを右カーソルに戻します。有効な例外を埋めないように、キャッチで再スローします。

try
{
    Cursor.Current = Cursors.WaitCursor;
    // Test something
    if (testResult) return;
    // Do something else
}
catch
{
    throw;
}
finally
{
     Cursor.Current = Cursors.Default;
}

1
歴史的にcatch必須の部分でしたtry...finallyか、それともこの例で機能的な役割を果たしていますか?-再確認したところ、try {} finally {}catchブロックがなくても使用できます。
セビ2018

2

あなたが投稿したコードの例では、実際には、例外がキャッチされても意味がありません。キャッチで何も行われず、再スローされるだけです。実際には、コールスタックが失われるので、害を及ぼします。 。

ただし、例外が発生した場合、例外をキャッチして、ロジックを実行して(ファイルロックのSQL接続を閉じる、または単にログを記録するなど)、処理する呼び出しコードに例外をスローします。ビジネスレイヤーを実装するコーダーで例外を処理する必要がある場合があるため、これはビジネスレイヤーではフロントエンドコードよりも一般的です。

繰り返しますが、投稿した例で例外をキャッチしても意味がありません。そのようにしないでください!


1

申し訳ありませんが、「改善されたデザイン」としての多くの例は依然としてひどいにおいがするか、非常に誤解を招く可能性があります。{}を試してみると{ログ; throw}はまったく意味がありません。例外ロギングは、アプリケーション内の中央の場所で行う必要があります。例外はとにかくスタックトレースを泡立たせます、なぜそれらをシステムの境界のどこかに、そして近くに記録しませんか?

コンテキスト(つまり、1つの例ではDTO)をログメッセージにシリアル化する場合は注意が必要です。ログファイルにアクセスできるすべての人の手に渡りたくない機密情報を簡単に含めることができます。また、例外に新しい情報を追加しないと、例外の折り返しのポイントがわかりません。古き良きJavaにはそのためのポイントがあり、呼び出し元は、コードを呼び出すときにどのような例外が予想されるかを知る必要があります。.NETにはこれがないため、ラッピングは、私が見たケースの少なくとも80%では何の効果もありません。


ジョーさん、ありがとうございました。Java(およびC#の場合)では、すべての例外(チェックされていない例外タイプを含む)をキャッチまたはスローするように宣言することを強制するクラスレベルのアノテーション@FaultBoundaryを見たいと思います。このアノテーションは、各アーキテクチャレイヤーのパブリックインターフェイスで使用します。したがって、@ FaultBoundary ThingDAOインターフェースは、SQLException、NPE、AIOBなどの実装の詳細をリークできません。代わりに、「原因となる」スタックトレースがログに記録され、DAOSystemExceptionがスローされます...システム例外を「永久に致命的」と定義します。
corlettk 2009

5
キャッチしてログに記録してから再スローする理由はたくさんあります。具体的には、catchログのあるメソッドに情報が含まれている場合、メソッドを終了すると失われます。エラーは後で処理される可能性がありますが、ログには記録されず、システムの欠陥に関する情報が失われます。
アンディ

1
これは、ExceptionクラスのDataプロパティが便利な場所です-一般的なログのローカル情報をすべてキャプチャします。もともと私の注意にそれをもたらしたこの記事: blog.abodit.com/2010/03/...
McGuireV10

1

他の人が言ったことに加えて、キャッチと再スローはノーオペレーションではないことを示す関連質問への私の回答を参照しください(VBにありますが、一部のコードはVBからC#で呼び出すことができます)。


このリンクで質問に答えることができますが、回答の重要な部分をここに含め、参照用のリンクを提供することをお勧めします。リンクされたページが変更されると、リンクのみの回答が無効になる可能性があります。- レビューから
LHIOUI、2018年

@HamzaLH、よく書かれた回答ではありませんが、他の回答や賛成票とは異なる情報があります。わかりません。なぜ削除するよう提案するのですか?「話題になっていて解決策を提供する短い答えはまだ答えです。」meta.stackexchange.com/questions/226258/…
Michael Freidgeim 2018年

これはリンクのみの回答です
LHIOUI 2018年

1.リンクのみの回答は、削除するのではなく、コメントに変更する必要があります。2.これは、他のSO質問への参照であり、外部サイトへの参照ではありません。時間の経過とともに壊れる可能性が低いと考えられています。3.追加の説明があるため、「リンクのみ」ではない-meta.stackexchange.com/questions/225370/…を
Michael Freidgeim

1

シナリオcatch-log-rethrowに関するほとんどの回答。

代わりにあなたのコードでそれを書き込むので、特に、AOPを使用することを検討しPostsharp.Diagnostic.Toolkit OnExceptionOptions IncludeParameterValueとIncludeThisArgumentで


このリンクで質問に答えることができますが、回答の重要な部分をここに含め、参照用のリンクを提供することをお勧めします。リンクされたページが変更されると、リンクのみの回答が無効になる可能性があります。- 口コミより
トニー・ドン

@TonyDong、私はそれがよく書かれた回答ではないことに同意しますが、他の回答や肯定的な投票とは異なる情報を持っています。わかりません。なぜ削除するよう提案するのですか?ところで、5年後のリンクはまだ有効です。「話題になっていて解決策を提供する短い答えはまだ答えです。」meta.stackexchange.com/questions/226258/…
Michael Freidgeim、2018年

Stackoverflowはこの提案だけを持っています。
トニー・ドン

@TonyDong、答えがまったく役に立たないというわけではない場合、「OKに見える」を選択する必要があります
Michael Freidgeim

0

経由の例外の再スロー throwは、現在の例外を処理する特定のコードがない場合、または特定のエラーケースを処理するロジックがあるが、他はすべてスキップする場合に便利です。

例:

string numberText = "";
try
{
    Console.Write("Enter an integer: ");
    numberText = Console.ReadLine();
    var result = int.Parse(numberText);

    Console.WriteLine("You entered {0}", result);
}
catch (FormatException)
{
    if (numberText.ToLowerInvariant() == "nothing")
    {
        Console.WriteLine("Please, please don't be lazy and enter a valid number next time.");
    }
    else
    {
        throw;
    }
}    
finally
{
    Console.WriteLine("Freed some resources.");
}
Console.ReadKey();

ただし、これを行う別の方法として、catchブロックで条件句を使用する方法もあります。

string numberText = "";
try
{
    Console.Write("Enter an integer: ");
    numberText = Console.ReadLine();
    var result = int.Parse(numberText);

    Console.WriteLine("You entered {0}", result);
}
catch (FormatException) when (numberText.ToLowerInvariant() == "nothing")
{
    Console.WriteLine("Please, please don't be lazy and enter a valid number next time.");
}    
finally
{
    Console.WriteLine("Freed some resources.");
}
Console.ReadKey();

.NETランタイムは例外オブジェクトを再スローする前に再構築する必要がないため、このメカニズムは例外を再スローするよりも効率的です。

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