Cómo para volver a iniciar InnerException sin perder el seguimiento de la pila en C#?

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

  •  09-06-2019
  •  | 
  •  

Pregunta

Hago un llamamiento, a través de la reflexión, un método que puede provocar una excepción.¿Cómo puedo pasar la excepción a mi interlocutor sin la envoltura de la reflexión pone a su alrededor?
Estoy regeneración de la InnerException, pero esto destruye la traza de la pila.
Ejemplo de código:

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;
    }
}
¿Fue útil?

Solución

En .NET 4.5 no es ahora el ExceptionDispatchInfo clase.

Esto permite capturar una excepción y volver a tirar de él sin cambiar la pila de seguimiento:

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

Esto funciona en cualquier excepción, no sólo AggregateException.

Fue introducido debido a la await El lenguaje C#, el cual se desenvuelve el interior de las excepciones de AggregateException las instancias con el fin de hacer el asincrónica características del lenguaje más parecido a la sincrónica funciones del lenguaje.

Otros consejos

Es es posible para preservar la traza de la pila antes de regeneración sin la reflexión:

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
}

Esto desperdicia una gran cantidad de ciclos en comparación a llamar InternalPreserveStackTrace a través de caché delegado, pero tiene la ventaja de basarse sólo en público funcionalidad.Aquí hay un par de común patrones de uso de la pila traza la preservación de las funciones:

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

Creo que su mejor apuesta sería sólo hay que poner esto en el bloque catch:

throw;

Y, a continuación, extraiga la innerexception más tarde.

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

Llamar al método de extensión en su excepción antes de tirarlo, que conservará el original de la traza de la pila.

Aún más la reflexión...

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

Tenga en cuenta que esto puede romper en cualquier momento, como en los campos privados no son parte de la API.Ver más discusión sobre Mono bugzilla.

Primero:no pierda la TargetInvocationException - es información valiosa cuando se desea depurar las cosas.
Segundo:Envuelva el EMPATE como InnerException en su propio tipo de excepción y poner OriginalException propiedad que se vincula a lo que usted necesita (y mantener toda la pila de llamadas intacta).
Tercero:Vamos a por el EMPATE de la burbuja de su método.

Nadie ha explicado la diferencia entre ExceptionDispatchInfo.Capture( ex ).Throw() y un plano throw, así que aquí está.

La forma completa para volver a iniciar una excepción es el uso de ExceptionDispatchInfo.Capture( ex ).Throw() (sólo disponible a partir de .Net 4.5).

A continuación están los casos es necesario poner a prueba este:

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

El caso 1 y el caso 2 le dará un seguimiento de la pila, donde el código fuente número de línea para la CallingMethod el método es el número de línea de la throw new Exception( "TEST" ) de la línea.

Sin embargo, el caso 3 se dará un seguimiento de la pila, donde el código fuente número de línea para la CallingMethod el método es el número de línea de la throw de la llamada.Esto significa que si el throw new Exception( "TEST" ) la línea está rodeado por otras operaciones, usted no tiene idea de en qué número de línea de la excepción se genera realmente.

Caso 4 es similar en el caso 2, porque el número de línea de la excepción original se conserva, pero no es un verdadero rethrow porque cambia el tipo de la excepción original.

Chicos, ustedes son cool..Voy a ser un nigromante pronto.

    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 código de ejemplo que utiliza excepción de serialización/deserialización.No requiere que el actual tipo de excepción para ser serializable.También se utiliza sólo público/protegido métodos.

    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 });
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top