C#でスタックトレースを失わずにInnerExceptionを再スローする方法は?


305

リフレクションを介して、例外を引き起こす可能性のあるメソッドを呼び出しています。ラッパーリフレクションが例外を配置せずに例外を呼び出し元に渡すにはどうすればよいですか?
InnerExceptionを再スローしていますが、これによりスタックトレースが破壊されます。
コード例:

public void test1()
{
    // Throw an exception for testing purposes
    throw new ArgumentException("test1");
}

void test2()
{
    try
    {
        MethodInfo mi = typeof(Program).GetMethod("test1");
        mi.Invoke(this, null);
    }
    catch (TargetInvocationException tiex)
    {
        // Throw the new exception
        throw tiex.InnerException;
    }
}

1
ブードゥー教を必要としない、これを行う別の方法があります。こちらの回答をご覧ください:stackoverflow.com/questions/15668334/…–
Timothy Shields

動的に呼び出されるメソッドでスローされる例外は、「呼び出しのターゲットによって例外がスローされました」例外の内部例外です。独自のスタックトレースがあります。心配することは他にあまりありません。
ajeh

回答:


481

では、.NET 4.5今そこにあるExceptionDispatchInfoクラス。

これにより、スタックトレースを変更せずに例外をキャプチャして再スローできます。

try
{
    task.Wait();
}
catch(AggregateException ex)
{
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}

これは、だけでなく、すべての例外で機能しますAggregateException

これは、非同期言語機能を同期言語機能のようにするためにawaitAggregateExceptionインスタンスからの内部例外をアンラップするC#言語機能により導入されました。


11
Exception.Rethrow()拡張メソッドの良い候補ですか?
nmarler 2014

8
ExceptionDispatchInfoクラスはSystem.Runtime.ExceptionServices名前空間にあり、.NET 4.5より前では使用できないことに注意してください。
ヨーヨー

53
throw;コンパイラは.Throw()が常に例外をスローすることを認識しないため、.Throw()行の後に通常のコードを配置する必要がある場合があります。throw;結果として呼び出されることは決してありませんが、少なくとも、メソッドが戻りオブジェクトを必要とするか、非同期関数である場合、コンパイラーは文句を言いません。
トッド14

5
@Taudrisこの質問は、特にによって特別に処理できない内部例外の再スローに関するものthrow;です。throw ex.InnerException;スタックトレースを使用すると、再スローされた時点で再初期化されます。
ポールターナー

5
@amitjhaExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw();
Vedran

86

リフレクションなしで再スローする前にスタックトレースを保持すること可能です。

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
}

これInternalPreserveStackTraceは、キャッシュされたデリゲート経由の呼び出しに比べて多くのサイクルを無駄にしますが、パブリック機能のみに依存するという利点があります。スタックトレース保持機能の一般的な使用パターンをいくつか次に示します。

// usage (A): cross-thread invoke, messaging, custom task schedulers etc.
catch (Exception e)
{
    PreserveStackTrace (e) ;

    // store exception to be re-thrown later,
    // possibly in a different thread
    operationResult.Exception = e ;
}

// usage (B): after calling MethodInfo.Invoke() and the like
catch (TargetInvocationException tiex)
{
    PreserveStackTrace (tiex.InnerException) ;

    // unwrap TargetInvocationException, so that typed catch clauses 
    // in library/3rd-party code can work correctly;
    // new stack trace is appended to existing one
    throw tiex.InnerException ;
}

これらの関数を実行した後、何をする必要がありますか?
vdboor 2010

2
実際には、呼び出しよりも遅くはありませんInternalPreserveStackTrace(10000回の反復で約6%遅くなります)。リフレクションによってフィールドに直接アクセスすることは、呼び出すよりも約2.5%高速ですInternalPreserveStackTrace
Thomas Levesque

1
e.Data文字列または一意のオブジェクトキーで辞書を使用することをお勧めします(ただしstatic readonly object myExceptionDataKey = new object ()、例外をどこかでシリアル化する必要がある場合は、これを行わないでください)。e.Messageパースするコードがどこかにある可能性があるため、変更を避けてくださいe.Message。解析e.Messageは悪ですが、例外的なプラクティスが不十分なサードパーティのライブラリを使用する必要がある場合など、他に選択肢がない場合があります。
Anton Tykhyy 2010年

10
DoFixupsは、シリアライゼーショントラクターがない場合、カスタム例外のために中断します
ruslander '16

3
例外にシリアル化コンストラクタがない場合、提案されたソリューションは機能しません。stackoverflow.com/a/4557183/209727で提案されているソリューションを使用することをお勧めします。.NET 4.5の場合は、ExceptionDispatchInfoクラスの使用を検討してください。
Davide Icardi 2013年

33

私はあなたの最善の策はこれをあなたのキャッチブロックに入れることだと思います:

throw;

そして、後でinnerexceptionを抽出します。


21
または、try / catchを完全に削除します。
Daniel Earwicker 2008

6
@Earwicker。try / catchを削除することは、例外をコールスタックに伝達する前にクリーンアップコードが必要な場合を無視するため、一般に良い解決策ではありません。
ヨルダン

12
@ジョーダン-クリーンアップコードは、catchブロックではなくfinallyブロックにある必要があります
Paolo

17
@Paolo-すべてのケースで実行されることになっている場合は、そうです。失敗した場合にのみ実行されることになっている場合は、できません。
チコドロ2010

4
InternalPreserveStackTraceはスレッドセーフではないので、これらの例外状態のいずれかに2つのスレッドがある場合は注意してください。
Rob

14

ExceptionDispatchInfo.Capture( ex ).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と似ていますが、元の例外のタイプを変更するため、実際の再スローではありません。


5
私はいつも「スロー」はスタックトレースをリセットしないと考えていました(「スローe」とは対照的です)。
Jesper Matthiesen 2017年

@JesperMatthiesen私は間違っているかもしれませんが、例外がスローされて同じファイルにキャッチされたかどうかに依存すると聞きました。同じファイルの場合はスタックトレースが失われ、別のファイルの場合は保持されます。
jahu

13
public static class ExceptionHelper
{
    private static Action<Exception> _preserveInternalException;

    static ExceptionHelper()
    {
        MethodInfo preserveStackTrace = typeof( Exception ).GetMethod( "InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic );
        _preserveInternalException = (Action<Exception>)Delegate.CreateDelegate( typeof( Action<Exception> ), preserveStackTrace );            
    }

    public static void PreserveStackTrace( this Exception ex )
    {
        _preserveInternalException( ex );
    }
}

例外をスローする前に、拡張メソッドを呼び出して、元のスタックトレースを保持します。


.Net 4.0では、InternalPreserveStackTraceが何も動作しないことに注意してください。Reflectorを見ると、メソッドが完全に空であることがわかります。
サミュエルジャック

スクラッチ:私はRCを見ていた:ベータ版では、実装を元に戻した!
サミュエルジャック

3
提案:PreserveStackTraceをexを返すように変更してから、例外をスローするには、次のように言うだけです:throw ex.PreserveStackTrace();
Simon_Weaver

なぜ使用Action<Exception>ここで静的メソッドを
Kiquenet '25年

10

さらに反射...

catch (TargetInvocationException tiex)
{
    // Get the _remoteStackTraceString of the Exception class
    FieldInfo remoteStackTraceString = typeof(Exception)
        .GetField("_remoteStackTraceString",
            BindingFlags.Instance | BindingFlags.NonPublic); // MS.Net

    if (remoteStackTraceString == null)
        remoteStackTraceString = typeof(Exception)
        .GetField("remote_stack_trace",
            BindingFlags.Instance | BindingFlags.NonPublic); // Mono

    // Set the InnerException._remoteStackTraceString
    // to the current InnerException.StackTrace
    remoteStackTraceString.SetValue(tiex.InnerException,
        tiex.InnerException.StackTrace + Environment.NewLine);

    // Throw the new exception
    throw tiex.InnerException;
}

非公開フィールドはAPIの一部ではないため、これはいつでも壊れる可能性があることに注意してください。Mono bugzillaに関する詳細な議論をご覧ください。


28
フレームワーククラスに関する文書化されていない内部の詳細に依存しているため、これは本当に非常に悪い考えです。
Daniel Earwicker 2008

1
リフレクションなしでスタックトレースを保持できることがわかりました。以下を参照してください。
Anton Tykhyy 2010年

1
内部InternalPreserveStackTraceメソッドの呼び出しは同じことを行い、将来変更される可能性が低いため、内部メソッドを呼び出す方が適切です...
Thomas Levesque

1
実際には、InternalPreserveStackTraceがMonoに存在しないため、状況は悪化します。
skolima

5
@ダニエル-まあそれは本当に、本当に、本当に悪いスローのアイデアです。すべての.net開発者がそうではないと信じるように訓練されているときにスタックトレースをリセットする。また、NullReferenceExceptionのソースを見つけることができず、それを見つけることができないために顧客/注文を失う場合、これは本当に、本当に、本当に悪いことです。私にとっては「文書化されていない詳細」よりもトランプであり、間違いなくモノです。
Simon_Weaver

10

まず、TargetInvocationExceptionを失わないようにしてください。デバッグするときに役立つ情報です。
次に、TIEを独自の例外タイプのInnerExceptionとしてラップし、必要なものにリンクするOriginalExceptionプロパティを配置します(コールスタック全体をそのままにします)。
3番目:メソッドからTIEをバブルアウトします。


5

みんな、かっこいい..すぐにネクロマンサーになる。

    public void test1()
    {
        // Throw an exception for testing purposes
        throw new ArgumentException("test1");
    }

    void test2()
    {
            MethodInfo mi = typeof(Program).GetMethod("test1");
            ((Action)Delegate.CreateDelegate(typeof(Action), mi))();

    }

1
いい考えですが、を呼び出すコードを常に制御するわけではありません.Invoke()
Anton Tykhyy 2010年

1
また、コンパイル時の引数/結果のタイプも常にわかるとは限りません。
ローマンスターコフ

3

例外のシリアル化/逆シリアル化を使用する別のサンプルコード。実際の例外タイプをシリアライズ可能にする必要はありません。また、パブリック/保護されたメソッドのみを使用します。

    static void PreserveStackTrace(Exception e)
    {
        var ctx = new StreamingContext(StreamingContextStates.CrossAppDomain);
        var si = new SerializationInfo(typeof(Exception), new FormatterConverter());
        var ctor = typeof(Exception).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, null);

        e.GetObjectData(si, ctx);
        ctor.Invoke(e, new object[] { si, ctx });
    }

実際の例外タイプをシリアライズ可能にする必要はありませんか?
Kiquenet 2018年

3

ポールターナーズの回答に基づいて、拡張メソッドを作成しました

    public static Exception Capture(this Exception ex)
    {
        ExceptionDispatchInfo.Capture(ex).Throw();
        return ex;
    }

これはreturn ex決して到達しませんが、利点はthrow ex.Capture()、コンパイラーがnot all code paths return a valueエラーを発生させないように、1つのライナーとして使用できることです。

    public static object InvokeEx(this MethodInfo method, object obj, object[] parameters)
    {
        {
            return method.Invoke(obj, parameters);
        }
        catch (TargetInvocationException ex) when (ex.InnerException != null)
        {
            throw ex.InnerException.Capture();
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.