.NET例外をキャッチして再スローするためのベストプラクティス


284

例外をキャッチして再スローするときに検討すべきベストプラクティスは何ですか?ExceptionオブジェクトInnerExceptionとスタックトレースが保持されることを確認したいと思います。次のコードブロックの処理方法に違いはありますか?

try
{
    //some code
}
catch (Exception ex)
{
    throw ex;
}

対:

try
{
    //some code
}
catch
{
    throw;
}

回答:


262

スタックトレースを保持する方法は、throw;これも有効です。

try {
  // something that bombs here
} catch (Exception ex)
{
    throw;
}

throw ex;基本的には、その時点から例外をスローするようなものなので、スタックトレースはthrow ex;ステートメントを発行している場所にしか行きません。

Mikeも正解です。例外により例外を渡すことができると想定しています(推奨)。

カール・セガンは持っている例外処理に大きな書き込みを彼に電子書籍をプログラミングの基礎偉大な読み取りであるだけでなく。

編集:プログラミングの基礎 pdf へのリンク。テキストで「例外」を検索してください。


10
その記述が素晴らしいかどうかはわかりませんが、試してみてください{// ...} catch(Exception ex){throw new Exception(ex.Message + "other stuff"); } いいね。問題は、すべての例外をキャッチしない限り、その例外をスタックの
上位で

2
@ljs彼が推奨するセクションが見当たらないため、コメント以降、記事が変更されました。実際にはまったく逆ですが、彼はそれをしないように言い、OutOfMemoryExceptionも処理するかどうかを尋ねます!?
RyanfaeScotland 2015年

6
時々投げる; スタックトレースを保持するには不十分です。https://dotnetfiddle.net/CkMFoX
Artavazd Balayan

4
またはExceptionDispatchInfo.Capture(ex).Throw(); throw;.NET 4.5でstackoverflow.com/questions/57383/...
アルフレッド・ウォレス

@AlfredWallaceソリューションは私にとって完璧に機能しました。try {...} catch {throw}はスタックトレースを保持しませんでした。ありがとうございました。
atownson

100

初期例外とともに新しい例外をスローすると、初期スタックトレースも保持されます。

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}

何をしようとしても、新しいException( "message"、ex)は常にexをスローし、カスタムメッセージを無視します。ただし、新しいException( "message"、ex.InnerException)をスローすることはできます。
Tod

何のカスタム例外が必要とされていない場合は1をAggregateException(.NET 4+)を使用することができますmsdn.microsoft.com/en-us/library/...
ニコスTsokos

AggregateException集約された操作の例外にのみ使用してください。たとえば、CLRのParallelEnumerableおよびTaskクラスによってスローされます。使用法はおそらくこの例に従うべきです。
アルアンハダード2017

29

実際、throwステートメントがStackTrace情報を保持しない状況がいくつかあります。たとえば、次のコードでは:

try
{
  int i = 0;
  int j = 12 / i; // Line 47
  int k = j + 1;
}
catch
{
  // do something
  // ...
  throw; // Line 54
}

StackTraceは、行54で例外が発生したが、行47で例外が発生したことを示します。

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.WithThrowIncomplete() in Program.cs:line 54
   at Program.Main(String[] args) in Program.cs:line 106

上記のような状況では、元のStackTraceを保持するための2つのオプションがあります。

Exception.InternalPreserveStackTraceの呼び出し

これはプライベートメソッドであるため、リフレクションを使用して呼び出す必要があります。

private static void PreserveStackTrace(Exception exception)
{
  MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
    BindingFlags.Instance | BindingFlags.NonPublic);
  preserveStackTrace.Invoke(exception, null);
}

StackTrace情報を保存するためにプライベートメソッドに依存するという欠点があります。.NET Frameworkの将来のバージョンで変更される可能性があります。上記のコード例と提案された解決策は、Fabrice MARGUERIEのウェブログから抽出されました。

Exception.SetObjectDataの呼び出し

以下の手法は、Anton TykhyyによってC#の回答として提案されました。スタックトレースの質問を失うことなく、InnerExceptionを再スローする方法を教えてください

static void PreserveStackTrace (Exception e) 
{ 
  var ctx = new StreamingContext  (StreamingContextStates.CrossAppDomain) ; 
  var mgr = new ObjectManager     (null, ctx) ; 
  var si  = new SerializationInfo (e.GetType (), new FormatterConverter ()) ; 

  e.GetObjectData    (si, ctx)  ; 
  mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData 
  mgr.DoFixups       ()         ; // ObjectManager calls SetObjectData 

  // voila, e is unmodified save for _remoteStackTraceString 
} 

ただし、パブリックメソッドのみに依存するという利点がありますが、これは次の例外コンストラクタにも依存します(サードパーティによって開発された一部の例外は実装していません)。

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

私の状況では、使用しているサードパーティライブラリによって発生した例外がこのコンストラクターを実装していないため、最初のアプローチを選択する必要がありました。


1
例外をキャッチして、この例外をどこにでも公開できます。次に、ユーザーに何が起こったかを説明する新しいものをスローします。このようにして、例外がキャッチされたときに何が起こったかを確認でき、ユーザーは実際の例外が何であったかを不注意に知ることができます。
Çöđěxěŕ

2
.NET 4.5には3つ目があり、私の意見では、よりクリーンなオプションがあり、ExceptionDispatchInfoを使用します。詳細については、こちらの関連​​する質問に対するTragediansの回答をご覧ください:stackoverflow.com/a/17091351/567000
セーレンBoisen

20

するとthrow ex、基本的に新しい例外がスローされ、元のスタックトレース情報を見逃してしまいます。 throw推奨される方法です。


13

経験則では、基本Exceptionオブジェクトのキャッチとスローを回避します。これにより、例外について少し賢くなります。言い換えるSqlExceptionと、処理コードがで問題を起こさないように、aを明示的にキャッチする必要がありますNullReferenceException

ただし、現実の世界では、ベース例外をキャッチしてログに記録することもお勧めしますが、すべてを調べて、例外を取得することを忘れないでくださいInnerExceptions


2
AppDomain.CurrentDomain.UnhandledExceptionおよびApplication.ThreadException例外を使用して、ロギングの目的で未処理の例外を処理するのが最善だと思います。あらゆる場所でビッグトライ{...} catch(Exception ex){...}ブロックを使用すると、多くの重複が発生します。処理された例外をログに記録するかどうかによって異なります。その場合、(少なくとも最小限の)重複は避けられない場合があります。
ljs

さらに、これらのイベントを使用すると、すべての未処理の例外をログに記録することになりますが、big ol 'try {...} catch(Exception ex){...}ブロックを使用すると、一部が失われる可能性があります。
ljs

10

常に「スロー」を使用する必要があります。.NETで例外を再スローするには、

これを参照してください、 http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

基本的に、MSIL(CIL)には、「スロー」と「再スロー」の2つの指示があります。

  • C#の「throw ex;」MSILの「スロー」にコンパイルされます
  • C#の「スロー」; -MSIL「再スロー」に!

基本的に、「throw ex」がスタックトレースをオーバーライドする理由を確認できます。


リンク(まあ、実際にはリンクが引用しているソース)は優れた情報でいっぱいであり、多くの人throw ex;がJavaで再スローすると思われる理由の可能性のある犯人も記しています-Javaではそうです!しかし、Aグレードの回答を得るために、ここその情報を含める必要があります。(私はまだExceptionDispatchInfo.Capturejeuoekdcwzfwccuから回答に追いついていますが)
ruffin

10

ExceptionDispatchInfo.Capture( ex ).Throw()とプレーンの違いを説明した人はいませんthrow。ただし、の問題に気づいた人もいthrowます。

キャッチされた例外を再スローする完全な方法は、使用することですExceptionDispatchInfo.Capture( ex ).Throw()(.Net 4.5からのみ利用可能)。

以下に、これをテストするために必要なケースがあります。

1。

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2。

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        ExceptionDispatchInfo.Capture( ex ).Throw();
        throw; // So the compiler doesn't complain about methods which don't either return or throw.
    }
}

3。

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}

4。

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}

ケース1とケース2は、CallingMethodメソッドのソースコード行番号が行の行番号であるスタックトレースを提供しますthrow new Exception( "TEST" )

ただし、ケース3では、CallingMethodメソッドのソースコード行番号がthrow呼び出しの行番号であるスタックトレースが得られます。これは、throw new Exception( "TEST" )行が他の操作に囲まれている場合、例外が実際にスローされた行番号がわからないことを意味します。

ケース4は、元の例外の行番号が保持されるという点でケース2と似ていますが、元の例外のタイプを変更するため、実際の再スローではありません。


単純な言い回しを追加して決して使用しないようthrow ex;にします。これがそれらすべての最良の答えです。
NH。

8

「throw」と「throw ex」は同じことをするかもしれないが、例外が発生した行である重要な情報を提供しない、非常に重要なポイントを実際に見逃した人もいます。

次のコードを検討してください。

static void Main(string[] args)
{
    try
    {
        TestMe();
    }
    catch (Exception ex)
    {
        string ss = ex.ToString();
    }
}

static void TestMe()
{
    try
    {
        //here's some code that will generate an exception - line #17
    }
    catch (Exception ex)
    {
        //throw new ApplicationException(ex.ToString());
        throw ex; // line# 22
    }
}

「throw」または「throw ex」のいずれかを実行すると、スタックトレースが表示されますが、line#は#22になるため、例外をスローしている行を正確に特定できません(1つまたはいくつかしかない場合を除く)。 tryブロックのコード行)。例外の期待される行#17を取得するには、元の例外スタックトレースで新しい例外をスローする必要があります。


3

次のものも使用できます。

try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}

スローされた例外は、それらを処理する次のレベルにバブルアップします。


3

私は間違いなく使用します:

try
{
    //some code
}
catch
{
    //you should totally do something here, but feel free to rethrow
    //if you need to send the exception up the stack.
    throw;
}

それはあなたのスタックを保持します。


1
2008年に公平に過ごすために、OPはスタックを保持する方法を尋ねていました。そして、2008年に正解しました。私の答えに欠けているのは、実際に漁獲物で何かをしている部分です。
1kevgriff、2015

@JohnSaundersこれは、以前に何もしなかった場合にのみ当てはまりますthrow。たとえば、使い捨てをクリーンアップして(エラー時にのみ呼び出す場合)、例外をスローすることができます。
Meirion Hughes

@meirionがコメントを書いたとき、スローの前には何もありませんでした。それが追加されたとき、私は賛成票を投じましたが、コメントは削除しませんでした。
ジョンサンダース

0

参考までに、これと「throw」によって報告されたスタックトレースをテストしました。完全に正しいスタックトレースではありません。例:

    private void foo()
    {
        try
        {
            bar(3);
            bar(2);
            bar(1);
            bar(0);
        }
        catch(DivideByZeroException)
        {
            //log message and rethrow...
            throw;
        }
    }

    private void bar(int b)
    {
        int a = 1;
        int c = a/b;  // Generate divide by zero exception.
    }

スタックトレースは例外の発生元を正しく指し示しますが(報告された行番号)、foo()に対して報告された行番号はスローの行です。ステートメントなので、bar()への呼び出しのどれが例外を引き起こしたかはわかりません。


そのため、例外を使用する予定がない限り、例外をキャッチしないのが最善です
Nate Zaugg
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.