2009-06-17 12 views
19

Duplicado de: In C#, how can I rethrow InnerException without losing stack trace?¿Cómo puedo volver a lanzar una excepción interna mientras mantengo el seguimiento de pila generado hasta ahora?

Tengo algunas operaciones que invoco asincrónicamente en una cadena de fondo. A veces, las cosas van mal. Cuando esto sucede, tiendo a obtener una TargetInvocationException, que, aunque es apropiada, es bastante inútil. Lo que realmente necesito es InnerException del TargetInvocationException, así:

try 
    { 
     ReturnValue = myFunctionCall.Invoke(Target, Parameters); 
    } 
    catch (TargetInvocationException err) 
    { 
     throw err.InnerException; 
    } 

De esa manera, las personas que llaman mis se sirven con la excepción de que se produjo VERDADERO. El problema es que la instrucción throw parece restablecer el seguimiento de la pila. Me gustaría básicamente replantear la excepción interna, pero mantener la pila que originalmente tenía. ¿Cómo puedo hacer eso?

ACLARACIÓN: La razón por la que quiero solamente la excepción interna es que esta clase intenta 'abstraer' todo el hecho de que estas funciones (delegados suministrados por la persona que llama) se ejecutan en otros temas y otras cosas. Si hay una excepción, entonces las probabilidades son que no tiene nada que ver con que se ejecute en un hilo de fondo, y al que llama realmente le gustaría el seguimiento de la pila que entra en su delegado y encuentra el problema real, no mi invocación.

+0

¿Por qué es TargetInvocationException inútil? Contiene la 'InnerException', por lo que tiene toda la información. –

+0

Respuesta actualizada, intente lanzar la suya utilizando la InnerException –

+0

. Hay otra forma de hacerlo que no requiere vudú. Eche un vistazo a la respuesta aquí: http://stackoverflow.com/questions/15668334/preserving-exceptions-from-dynamically-invoked-methods –

Respuesta

29

Se es posible preservar el seguimiento de la pila antes de Regeneración de sin 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 con InternalPreserveStackTrace, pero tiene la ventaja de confiar solamente en la funcionalidad pública. Aquí hay un par de patrones de uso comunes para las funciones de pila-trace preservando:

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

top banana! esto es aceness –

+1

perder ciclos cuando tiene una excepción vale la pena. Entonces, ¿cómo resolviste esto? ¡Gracias por la publicacion! –

+0

Gran respuesta. Sin embargo, tenga en cuenta que 'DoFixups()' [lanzará SerializationExceptions] (http://stackoverflow.com/questions/3086234), en caso de que una clase de excepción no sea deserializable. Por lo tanto, es posible que desee envolver el cuerpo de 'PreserveStackTrace' en un gran' try/catch'. –

6

No, eso no es posible. Su única oportunidad real es seguir el patrón recomendado y lanzar su propia excepción con el InnerException apropiado.

Editar

Si su preocupación es la presencia de la TargetInvocationException y quiere hacer caso omiso de él (no es que yo recomendaría, ya que podría muy bien tener algo que ver con el hecho de que está siendo ejecutar en otro hilo) entonces nada le impide arrojar su propia excepción aquí y adjuntar el InnerException desde el TargetInvocationException como su propio InnerException. Es un poco apestoso, pero podría lograr lo que quieres.

+0

It * is * possible, ver a continuación. –

+0

@ Anton: Eso es definitivamente un hallazgo interesante. Realmente no me gusta la idea de poder quitar una excepción externa, incluso 'TargetInvocationException', pero claramente hay una porción significativa de la comunidad que sí lo hace. –

+1

Sí, porque a menudo no hay otra forma: la biblioteca y el código de terceros rara vez entienden 'TargetInvocationException' y otros, e incluso en nuestro propio código manejarlo donde sea que pueda surgir no deja de ser una molestia, con un tipo de excepción verifica la duplicación de las cláusulas de captura y esas cosas. –

0

No puede hacer eso. throw siempre restablece el seguimiento de la pila, a menos que se utilice sin el parámetro. Me temo que las personas que llaman tendrán que usar InnerException ...

0

El uso de la palabra clave "throw" con una excepción siempre restablecerá el seguimiento de la pila.

Lo mejor que puede hacer es detectar la excepción real que desee y utilizar "arrojar"; en lugar de "throw ex;" O lanzar tu propia excepción, con la InnerException que deseas pasar.

No creo que lo que quieres hacer es posible.

2

Aunque puede sentir que TargetInvocationException es "inútil", es la realidad. No trates de fingir que .NET no tomó la excepción original y la envuelve con una TargetInvocationException y la lanza. Eso realmente sucedió Algún día, es posible que incluso desee obtener algún tipo de información de ese envoltorio, como la ubicación del código que lanzó TargetInvocationException.

+0

La ubicación del re-lanzador es siempre un valor inútil en cada pieza que he visto. – Joshua

5

Hay una manera de "restablecer" el seguimiento de pila en una excepción mediante el mecanismo interno que se utiliza para conservar trazas de pila del lado del servidor cuando se utiliza interacción remota, pero es horrible:

try 
{ 
    // some code that throws an exception... 
} 
catch (Exception exception) 
{ 
    FieldInfo remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); 
    remoteStackTraceString.SetValue(exception, exception.StackTrace); 
    throw exception; 
} 

Esto pone la traza original de la pila en el campo _remoteStackTraceString de la excepción, que se concatena a la nueva pila de reinicio cuando se vuelve a lanzar la excepción.

Esto es realmente un hack horrible, pero logra lo que desea. Sin embargo, está retocando dentro de la clase System.Exception, por lo que este método puede por lo tanto romperse en versiones posteriores del marco.

+2

esto es asombroso, cuando tienes una * genuina * necesidad de volver a lanzar una excepción en un lugar diferente, pero mantén el viejo rastro de pila (como en un marco de prueba de unidad) –

0

Como han dicho otros, utilice la palabra clave "throw" sin agregarla para mantener intacta la cadena de excepciones. Si necesita esa excepción original (suponiendo que eso es lo que quiere decir), puede llamar a Exception.GetBaseException() al final de su cadena para obtener la Excepción que lo inició todo.

0

Es posible con .NET 4.5:

catch(Exception e) 
{ 
    ExceptionDispatchInfo.Capture(e.InnerException).Throw(); 
} 
Cuestiones relacionadas