Comment rediffuser InnerException sans perdre la trace de stack en C #?

J’appelle, par reflection, une méthode pouvant provoquer une exception. Comment puis-je transmettre l’exception à mon appelant sans la reflection enveloppante mise autour?
Je rejoue l’InnerException, mais cela détruit la trace de la stack.
Exemple de code:

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

Dans .NET 4.5, il existe maintenant la classe ExceptionDispatchInfo .

Cela vous permet de capturer une exception et de la relancer sans changer la trace de stack:

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

Cela fonctionne avec n’importe quelle exception, pas seulement avec AggregateException .

Il a été introduit en raison de la fonctionnalité de langage await C #, qui décompresse les exceptions internes des instances AggregateException afin que les fonctionnalités de langage asynchrone ressemblent davantage aux fonctionnalités de langage synchrone.

Il est possible de conserver la trace de la stack avant de renvoyer sans reflection:

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

Cela gaspille beaucoup de cycles par rapport à l’appel à InternalPreserveStackTrace via un délégué mis en cache, mais présente l’avantage de ne s’appuyer que sur des fonctionnalités publiques. Voici quelques modèles d’utilisation courants pour les fonctions de préservation de la trace 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 ; } 

Je pense que votre meilleur pari serait de simplement mettre ceci dans votre bloc de capture:

 throw; 

Et puis extraire l’innéerexception plus tard.

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

Appelez la méthode d’extension sur votre exception avant de la lancer, cela préservera la trace de la stack d’origine.

Encore plus de reflection …

 catch (TargetInvocationException tiex) { // Get the _remoteStackTraceSsortingng of the Exception class FieldInfo remoteStackTraceSsortingng = typeof(Exception) .GetField("_remoteStackTraceSsortingng", BindingFlags.Instance | BindingFlags.NonPublic); // MS.Net if (remoteStackTraceSsortingng == null) remoteStackTraceSsortingng = typeof(Exception) .GetField("remote_stack_trace", BindingFlags.Instance | BindingFlags.NonPublic); // Mono // Set the InnerException._remoteStackTraceSsortingng // to the current InnerException.StackTrace remoteStackTraceSsortingng.SetValue(tiex.InnerException, tiex.InnerException.StackTrace + Environment.NewLine); // Throw the new exception throw tiex.InnerException; } 

N’oubliez pas que cela peut être interrompu à tout moment, car les champs privés ne font pas partie de l’API. Voir plus de détails sur Mono bugzilla .

Premièrement: ne perdez pas la TargetInvocationException – ce sont des informations précieuses lorsque vous souhaitez déboguer des éléments.
Deuxièmement: placez la TIE sous la forme InnerException dans votre propre type d’exception et insérez une propriété OriginalException qui renvoie à ce dont vous avez besoin (et conservez l’intégralité de la stack d’appel).
Troisièmement: Laissez le TIE sortir de votre méthode.

Personne n’a expliqué la différence entre ExceptionDispatchInfo.Capture( ex ).Throw() et un throw simple, alors la voici.

La méthode complète pour renvoyer une exception interceptée consiste à utiliser ExceptionDispatchInfo.Capture( ex ).Throw() (uniquement disponible à partir de .Net 4.5).

Ci-dessous, les cas nécessaires pour tester ceci:

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

Les cas 1 et 2 vous donneront une trace de stack où le numéro de ligne du code source pour la méthode CallingMethod est le numéro de ligne de la ligne de CallingMethod de la throw new Exception( "TEST" ) .

Cependant, le cas 3 vous donnera une trace de stack où le numéro de ligne du code source pour la méthode CallingMethod est le numéro de ligne de l’appel à throw . Cela signifie que si la ligne de throw new Exception( "TEST" ) est entourée d’autres opérations, vous ne savez pas à quel numéro de ligne l’exception a réellement été levée.

Le cas 4 est similaire au cas 2 car le numéro de ligne de l’exception d’origine est préservé, mais il ne s’agit pas d’un véritable renvoi car il modifie le type de l’exception d’origine.

Les gars, vous êtes cool .. Je vais être un nécromancien bientôt.

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

Un exemple de code qui utilise la sérialisation / désérialisation des exceptions. Il n’est pas nécessaire que le type d’exception réel soit sérialisable. En outre, il utilise uniquement des méthodes publiques / protégées.

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