364 Stimmen

Wie InnerException ohne Verlust von Stack-Trace in C# neu auslösen?

Ich rufe über Reflexion eine Methode auf, die eine Ausnahme verursachen kann. Wie kann ich die Ausnahme an meinen Aufrufer weitergeben, ohne dass sie von der Reflexion umschlossen wird?
Ich werfe die InnerException erneut aus, aber dadurch wird die Stapelverfolgung zerstört.
Beispiel-Code:

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

581voto

Paul Turner Punkte 37044

Unter .NET 4.5 gibt es jetzt die ExceptionDispatchInfo Klasse.

Damit können Sie eine Ausnahme erfassen und erneut auslösen, ohne den Stack-Trace zu ändern:

using ExceptionDispatchInfo = 
    System.Runtime.ExceptionServices.ExceptionDispatchInfo;

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

Dies funktioniert bei jeder Ausnahme, nicht nur bei AggregateException .

Sie wurde eingeführt aufgrund der await C#-Sprachfunktion, die die inneren Ausnahmen von AggregateException Instanzen, um die asynchronen Sprachfunktionen den synchronen Sprachfunktionen anzunähern.

88voto

Anton Tykhyy Punkte 18869

Es est möglich, den Stack-Trace zu erhalten, bevor er ohne Reflexion erneut ausgelöst wird:

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
}

Dies verschwendet eine Menge Zyklen im Vergleich zum Aufruf von InternalPreserveStackTrace über einen zwischengespeicherten Delegaten, hat aber den Vorteil, dass er sich nur auf öffentliche Funktionen stützt. Hier sind ein paar gängige Verwendungsmuster für Funktionen, die die Stack-Trace-Funktionalität erhalten:

// 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 ;
}

36voto

GEOCHET Punkte 20824

Ich denke, das Beste wäre, wenn Sie dies einfach in Ihren Fangblock einbauen:

throw;

Und extrahieren Sie dann später die Innerexception.

31voto

jeuoekdcwzfwccu Punkte 399

Niemand hat den Unterschied zwischen ExceptionDispatchInfo.Capture( ex ).Throw() und eine einfache throw Hier ist sie also.

Die vollständige Art und Weise, eine abgefangene Ausnahme erneut auszulösen, ist die Verwendung von ExceptionDispatchInfo.Capture( ex ).Throw() (erst ab .Net 4.5 verfügbar).

Nachfolgend sind die Fälle aufgeführt, die für die Prüfung dieser Frage erforderlich sind:

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

In den Fällen 1 und 2 erhalten Sie einen Stack-Trace, in dem die Quellcode-Zeilennummer für die CallingMethod Methode ist die Zeilennummer der throw new Exception( "TEST" ) Linie.

In Fall 3 erhalten Sie jedoch einen Stack-Trace, in dem die Quellcode-Zeilennummer für die CallingMethod Methode ist die Zeilennummer der throw anrufen. Dies bedeutet, dass, wenn der throw new Exception( "TEST" ) Zeile von anderen Operationen umgeben ist, haben Sie keine Ahnung, bei welcher Zeilennummer die Ausnahme tatsächlich ausgelöst wurde.

Fall 4 ist ähnlich wie Fall 2, da die Zeilennummer der ursprünglichen Ausnahme erhalten bleibt, aber es handelt sich nicht um einen echten Rückruf, da der Typ der ursprünglichen Ausnahme geändert wird.

15voto

Eric Punkte 141
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 );
    }
}

Rufen Sie die Erweiterungsmethode für Ihre Ausnahme auf, bevor Sie sie auslösen, so bleibt der ursprüngliche Stack-Trace erhalten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X