web-dev-qa-db-ja.com

C#で例外をキャッチして再スローするのはなぜですか。

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

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

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!
}

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

DO

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と同じように)特定性の低い例外の前に、特定性の高い例外をキャッチします。


参考文献:

506
corlettk

最初;記事のコードのやり方は悪です。 throw exは、例外内の呼び出しスタックをこのthrow文が存在する位置までリセットします。例外が実際に作成された場所に関する情報を失う。

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

ただし、例外をキャッチして再スローしたい場合があります。ロギングはそのうちの1つです。

try 
{
    // code that may throw exceptions    
}
catch(Exception ex) 
{
    // add error logging here
    throw;
}
395
Fredrik Mörk

しないで

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

あなたはスタックトレース情報を失うでしょう...

どちらでも

try { ... }
catch { throw; }

OR

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
}
108
Eoin Campbell

C#(C#6より前)はVBがサポートしているCILの "フィルタ例外"をサポートしていません。実際に例外をキャッチしたいかどうかを決定するためのcatch()の時間。

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

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

...これは、異なるErrorCode値を持つMyExceptionを処理しません。 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
}
52
bzlm

私のようなコードを持っている主な理由:

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

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

デバッグ中はいいですね。

13
user175440

例外を再スローする正当な理由は、あなたが例外に情報を追加したいか、あるいはオリジナルの例外をあなた自身の作成の1つでラップしたいということかもしれません。

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);
  }
}
11
edosoft

これはまったく例外を処理しないこととまったく同じではないでしょうか。

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

10
Arjan Einbu

あなたはexを投げたくありません - これは呼び出しスタックを失うので。 例外処理(MSDN)を参照してください。

そして、そうです、try ... catchは何も役に立ちません(呼び出しスタックを失うことを除いて - だから実際にはもっと悪いです - 何らかの理由でこの情報を公開したくないのでなければ)。

8
Duncan

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

「処理」できるものだけを「キャッチ」するという一般的な知恵のため、例外が発生したときにアクションを実行する必要のある多くのコードはそうではありません。たとえば、多くのコードがロックを取得し、保護されたオブジェクトを「一時的に」その不変条件に違反する状態にし、オブジェクトを正当な状態にし、他の人がオブジェクトを見る前にロックを解除します。オブジェクトが危険なほど無効な状態にあるときに例外が発生した場合、一般的には、オブジェクトがまだその状態にある状態でロックを解除します。より良いパターンは、オブジェクトが「危険な」状態にあるときに発生する例外を明示的に無効にすることで、ロックを取得しようとすると、すぐに失敗します。このようなパターンを一貫して使用すると、いわゆる「ポケモン」例外処理の安全性が大幅に向上します。これは、主に適切なアクションを実行せずに例外が浸透するコードが原因で評判が悪い評判です。

ほとんどの.NET言語では、コードが例外に基づいてアクションを実行する唯一の方法は、catch(例外を解決しないことを知っていても)、問題のアクションを実行してから、throwを実行することです。コードがスローされる例外を気にしない場合の別の可能なアプローチは、try/finallyブロックでokフラグを使用することです。 okフラグを、ブロックの前のfalse、ブロックの終了前、およびブロック内のtrueの前のreturnに設定します。次に、finally内で、okが設定されていない場合、例外が発生しているに違いないと想定します。このようなアプローチは、catch/throwよりも意味的には優れていますが、く、本来よりも保守性が低くなります。

5
supercat

他の回答の多くは、例外の再スローをキャッチしたい理由の良い例を示していますが、 'finally'シナリオについて言及した人はいないようです。

この例としては、カーソルを設定するメソッド(たとえば待機カーソル)があり、そのメソッドにいくつかの終了ポイント(たとえば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;
}
3
statler

それはあなたがcatchブロックの中で何をしているのか、そしてエラーを呼び出しコードに渡したいのかどうかに依存します。

Catch io.FileNotFoundExeption exと言ってから別のファイルパスなどを使用することもできますが、それでもエラーが発生します。

また、Throw Exの代わりにThrowを実行すると、フルスタックトレースを維持できます。 Throw exはthrow文からスタックトレースを再開します(私はそれが理にかなっていると思います)。

3
Pondidum

あなたのプログラミングがライブラリまたはdllのために機能するとき、これは役に立ちます。

この再スロー構造は、関数内の個々の関数からスローされた例外を見る代わりに、関数自体から例外を受け取るように、意図的に呼び出しスタックをリセットするために使用できます。

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

3
Jackson Tarisa

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

3
Brian

あなたが投稿したコードの例では、実際には、単に再スローされたキャッチに対しては何もしていないので、例外をキャッチする意味はありません。 。

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

投稿した例では例外をキャッチすることには意味がありませんが、繰り返します。そんなことしないで!

2
Sheff

ほとんどの回答はシナリオのcatch-log-rethrowについて話しています。

それをあなたのコードで書く代わりに、特に Postsharp.Diagnostic.Toolkit とOnExceptionOptions IncludeParameterValueとIncludeThisArgumentを使うことを考慮してください

1

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

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

1
Joe

他の人が言ったことに加えて、 関連する質問に対する私の答え を見てください。しかし、コードの中にはVBから呼び出されるC#のものもあります。

1
erikkallen