Jak zmienić InnerException bez utraty stack trace w C#?

[1]}nazywam, przez refleksję, metodą, która może spowodować wyjątek. Jak mogę przekazać wyjątek mojemu rozmówcy bez odbicia owijki wokół niego?
Zmieniam koncepcję InnerException, ale to niszczy ślad stosu.
Przykładowy kod:

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;
    }
}
Author: shA.t, 2008-09-11

9 answers

W . NET 4.5 jest teraz ExceptionDispatchInfo klasy.

Pozwala to przechwycić wyjątek i rzucić go ponownie bez zmiany śladu stosu:

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

To działa na każdym wyjątku, nie tylko AggregateException.

Został wprowadzony z powodu funkcji języka C# await, która rozpakowuje wewnętrzne wyjątki z instancji AggregateException, aby funkcje języka asynchronicznego były bardziej podobne do funkcji języka synchronicznego.

 371
Author: Paul Turner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-15 12:35:10

To jest możliwe, aby zachować ślad stosu przed ponownym przeglądaniem bez refleksji:

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
}

To marnuje wiele cykli w porównaniu z wywołaniem InternalPreserveStackTrace przez buforowanego delegata, ale ma tę zaletę, że polega tylko na publicznej funkcjonalności. Oto kilka typowych wzorców użycia dla funkcji zachowujących stos:

// 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 ;
}
 84
Author: Anton Tykhyy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-19 21:43:34

Myślę, że najlepiej byłoby umieścić to w bloku haczyk:

throw;

A następnie wyodrębnić innerexception później.

 32
Author: GEOCHET,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-08 18:45:56
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 );
    }
}

Wywołanie metody rozszerzenia na Twoim wyjątku przed rzuceniem, zachowa oryginalny ślad stosu.

 12
Author: Eric,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-09 05:31:49

Jeszcze więcej refleksji...

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

Należy pamiętać, że może to ulec awarii w dowolnym momencie, ponieważ pola prywatne nie są częścią API. Zobacz dalszą dyskusję na temat mono bugzilla .

 11
Author: skolima,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-18 12:18:27

Po pierwsze: nie trać TargetInvocationException - to cenne informacje, gdy będziesz chciał debugować rzeczy.
Po drugie: zawiń krawat jako InnerException we własnym typie wyjątku i umieść właściwość OriginalException, która łączy się z tym, czego potrzebujesz (i zachowuje cały callstack nienaruszony).
Po trzecie: niech bańka krawat z metody.

 10
Author: kokos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-15 19:32:57

Nikt nie wyjaśnił różnicy między ExceptionDispatchInfo.Capture( ex ).Throw() a zwykłym throw, więc oto ona.

Kompletnym sposobem na zmianę przechwyconego wyjątku jest użycie ExceptionDispatchInfo.Capture( ex ).Throw() (dostępne tylko z. Net 4.5).

Poniżej znajdują się przypadki niezbędne do przetestowania tego:

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

Przypadek 1 i Przypadek 2 dadzą ślad stosu, w którym numer linii kodu źródłowego metody CallingMethod jest numerem linii throw new Exception( "TEST" ) linia.

Jednak przypadek 3 daje ślad stosu, w którym numer linii kodu źródłowego metody CallingMethod jest numerem linii wywołania throw. Oznacza to, że jeśli linia throw new Exception( "TEST" ) jest otoczona innymi operacjami, nie masz pojęcia, na który numer linii został wyrzucony wyjątek.

Przypadek 4 jest podobny do przypadku 2, ponieważ numer linii oryginalnego wyjątku jest zachowany, ale nie jest prawdziwym rethrow, ponieważ zmienia typ oryginalnego wyjątku.

 8
Author: jeuoekdcwzfwccu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-14 13:39:00

Chłopaki, jesteście spoko.. Wkrótce zostanę nekromantą.

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

    }
 5
Author: Boris Treukhov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-02 17:48:10

Inny przykładowy kod, który używa WYJĄTKÓW serializacja/deserializacja. Nie wymaga, aby rzeczywisty typ wyjątku był serializowalny. Ponadto używa tylko publicznych / chronionych metod.

    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 });
    }
 3
Author: chickenbyproduct,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-03 08:19:24