Wie kann ich InnerException erneut auslösen, ohne den Stack-Trace in C# zu verlieren?

StackOverflow https://stackoverflow.com/questions/57383

  •  09-06-2019
  •  | 
  •  

Frage

Ich rufe durch Reflexion eine Methode auf, die eine Ausnahme verursachen kann.Wie kann ich die Ausnahme an meinen Anrufer weitergeben, ohne dass die Wrapper-Reflexion sie umgibt?
Ich löse die InnerException erneut aus, aber dadurch wird der Stack-Trace zerstört.
Beispielcode:

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;
    }
}
War es hilfreich?

Lösung

.NET 4.5 gibt es jetzt die ExceptionDispatchInfo Klasse.

Auf diese Weise können Sie eine Ausnahme erfassen und wieder werfen sie ohne Änderung der Stack-Trace:

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

Dies funktioniert auf jeder Ausnahme, nicht nur AggregateException.

Es wurde # Sprache-Funktion eingeführt aufgrund der await C, die die inneren Ausnahmen von AggregateException Instanzen, um auspackt die asynchronen Sprachfunktionen mehr wie die synchronen Sprachfunktionen zu machen.

Andere Tipps

Es ist möglich, das Stack-Trace zu erhalten, bevor Erneutes Auslösen ohne Reflexion:

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 von Zyklen im Vergleich zu InternalPreserveStackTrace über gecached Delegaten ruft, hat aber den Vorteil, nur auf öffentliche Funktionalität angewiesen zu sein. Hier sind ein paar gemeinsamen Nutzungsmuster für Stack-Trace-Erhaltung Funktionen:

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

Ich denke, Ihre beste Wette nur sei dies in Ihrem Catch-Block zu setzen:

throw;

Und dann extrahieren Sie die Innerexception später.

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 auf Ihrer Ausnahme, bevor Sie es zu werfen, wird es das ursprüngliche Stack-Trace erhalten.

Noch mehr Reflexion ...

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

Beachten Sie, dass diese jederzeit brechen kann, als private Felder sind nicht Teil der API. Sehen Sie weitere Diskussion über Mono Bugzilla .

Erstens:. Sie das TargetInvocationException nicht verlieren - es ist wertvolle Informationen, wenn Sie debuggen Dinge wollen
Zweitens: Wickeln Sie die TIE als Innerexception in Ihrem eigenen Ausnahmetyp und eine OriginalException Eigenschaft setzen, zu denen Links, was Sie brauchen (und halten die gesamte Aufrufliste intakt)
. Drittens:. Die TIE-Blase aus Ihrer Methode Let

Niemand hat den Unterschied zwischen ExceptionDispatchInfo.Capture( ex ).Throw() und einem einfachen throw erklärt, so ist es hier.

Der komplette Weg, um eine abgefangene Ausnahme erneut auslösen ist ExceptionDispatchInfo.Capture( ex ).Throw() zu verwenden (nur von .Net 4.5).

Im Folgenden gibt es die Fälle notwendig, dies zu testen:

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

Fall 1 und Fall 2 geben Ihnen einen Stack-Trace, wo die Sourcecode-Zeilennummer für die CallingMethod Methode der Zeilennummer der throw new Exception( "TEST" ) Linie ist.

Allerdings Fall 3 werden Sie einen Stack-Trace geben, wo die Sourcecode-Zeilennummer für die CallingMethod Methode die Zeilennummer des throw Anruf ist. Das bedeutet, dass, wenn die throw new Exception( "TEST" ) Linie von anderen Operationen umgeben ist, haben Sie keine Ahnung haben, auf die Ausnahme, die Zeilennummer tatsächlich ausgelöst wurde.

Case 4 ist ähnlich mit Fall 2, da die Zeilennummer der ursprünglichen Ausnahme erhalten bleibt, ist aber kein wirkliches rethrow, weil es die Art der ursprünglichen Ausnahme ändert.

Jungs, Sie sind cool .. Ich werde bald ein Nekromant sein.

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

    }

Anpother Codebeispiel der Ausnahme Serialisierung / Deserialisierung verwendet. Es ist nicht den tatsächlichen Ausnahmetyp erfordert serializable zu sein. Auch verwendet er nur öffentliche / geschützte Methoden.

    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 });
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top