En C #, ¿cómo puedo volver a lanzar InnerException sin perder el seguimiento de stack?

Estoy llamando, a través de la reflexión, un método que puede causar una excepción. ¿Cómo puedo pasar la excepción a la persona que llama sin que el reflection de la envoltura lo coloque? Estoy volviendo a lanzar la InnerException, pero esto destruye el seguimiento de la stack. Código de ejemplo:

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

En .NET 4.5 ahora existe la clase ExceptionDispatchInfo .

Esto le permite capturar una excepción y volver a lanzarla sin cambiar el stack-trace:

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

Esto funciona con cualquier excepción, no solo con AggregateException .

Se introdujo debido a la función de lenguaje C #, que desenvuelve las excepciones internas de las instancias de AggregateException para hacer que las funciones del lenguaje asíncrono se parezcan más a las características del lenguaje síncrono.

Es posible preservar la traza de stack antes de volver a lanzar 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 consume muchos ciclos en comparación con llamar a InternalPreserveStackTrace través de un delegado en caché, pero tiene la ventaja de depender únicamente de la funcionalidad pública. Aquí hay un par de patrones de uso comunes para las funciones de preservación de seguimiento de stack:

 // 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 tu mejor opción sería poner esto en tu bloque catch:

 throw; 

Y luego extrae la innerecepción más tarde.

 public static class ExceptionHelper { private static Action _preserveInternalException; static ExceptionHelper() { MethodInfo preserveStackTrace = typeof( Exception ).GetMethod( "InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic ); _preserveInternalException = (Action)Delegate.CreateDelegate( typeof( Action ), preserveStackTrace ); } public static void PreserveStackTrace( this Exception ex ) { _preserveInternalException( ex ); } } 

Llame al método de extensión en su excepción antes de tirarlo, preservará el seguimiento original de la stack.

Aún más 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 romperse en cualquier momento, ya que 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 querrá depurar cosas.
Segundo: ajuste el TIE como InnerException en su propio tipo de excepción y ponga una propiedad OriginalException que vincule con lo que necesita (y mantenga todo el conjunto de llamadas intacto).
Tercero: Deja que el TIE salga de tu método.

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

La forma completa de volver a lanzar una excepción atrapada es usar ExceptionDispatchInfo.Capture( ex ).Throw() (solo disponible desde .Net 4.5).

A continuación están los casos necesarios para probar esto:

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án un seguimiento de stack donde el número de línea del código fuente para el método CallingMethod es el número de línea de la throw new Exception( "TEST" ) .

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

El caso 4 es similar con el caso 2 porque el número de línea de la excepción original se conserva, pero no es un nuevo lanzamiento porque cambia el tipo de la excepción original.

Chicos, son geniales … Pronto seré nigromante.

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

Otro código de muestra que usa serialización / deserialización de excepciones. No requiere que el tipo de excepción real sea serializable. También usa solo métodos públicos / protegidos.

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