Question

J'appelle, par réflexion, une méthode qui peut provoquer une exception.Comment puis-je transmettre l'exception à mon appelant sans la réflexion du wrapper qui l'entoure ?
Je relance l'InnerException, mais cela détruit la trace de la pile.
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;
    }
}
Était-ce utile?

La solution

Dans .NET 4.5 il y a maintenant le ExceptionDispatchInfo classe.

Cela vous permet de capturer une exception et de la relancer sans modifier la trace de la pile :

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

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

Il a été introduit en raison de await Fonctionnalité du langage C#, qui déballe les exceptions internes de AggregateException instances afin de rendre les fonctionnalités du langage asynchrone plus proches des fonctionnalités du langage synchrone.

Autres conseils

Il est possible de conserver la trace de la pile avant de relancer sans réflexion :

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
}

Cela fait perdre beaucoup de cycles par rapport à l'appel InternalPreserveStackTrace via un délégué mis en cache, mais présente l'avantage de s'appuyer uniquement sur des fonctionnalités publiques.Voici quelques modèles d'utilisation courants des fonctions de préservation de la trace de la pile :

// 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 le mieux serait de simplement mettre ceci dans votre bloc catch :

throw;

Et puis extrayez l’exception interne plus tard.

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

Appelez la méthode d'extension sur votre exception avant de la lancer, elle conservera la trace de la pile d'origine.

Encore plus de réflexion...

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

Gardez à l'esprit que cela peut être interrompu à tout moment, car les champs privés ne font pas partie de l'API.Voir la discussion plus approfondie sur Bugzilla mono.

D'abord:ne perdez pas l'exception TargetInvocationException - ce sont des informations précieuses lorsque vous souhaitez déboguer des choses.
Deuxième:Enveloppez le TIE en tant qu'InnerException dans votre propre type d'exception et placez une propriété OriginalException qui renvoie à ce dont vous avez besoin (et conservez l'intégralité de la pile d'appels intacte).
Troisième:Laissez le TIE sortir de votre système.

Personne n'a expliqué la différence entre ExceptionDispatchInfo.Capture( ex ).Throw() et une plaine throw, alors le voici.

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

Vous trouverez ci-dessous les cas nécessaires pour tester cela :

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

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

Cependant, le cas 3 vous donnera une trace de pile où le numéro de ligne du code source pour le CallingMethod méthode est le numéro de ligne du throw appel.Cela signifie que si le throw new Exception( "TEST" ) La ligne est entourée d'autres opérations, vous n'avez aucune idée du numéro de ligne auquel l'exception a été réellement levée.

Le cas 4 est similaire au cas 2 car le numéro de ligne de l'exception d'origine est conservé, mais il ne s'agit pas d'une véritable relance car il modifie le type de l'exception d'origine.

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

    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 autre 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.De plus, 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 });
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top