c# - uma - struct pilha




Como retroceder InnerException sem perder rastreamento de pilha em c#? (6)

É possível preservar o rastreamento de pilha antes de recriar sem reflexão:

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
}

Isso desperdiça muitos ciclos comparado a chamar o InternalPreserveStackTrace por meio do delegado em cache, mas tem a vantagem de confiar apenas na funcionalidade pública. Aqui estão alguns padrões de uso comuns para funções de preservação de rastreamento de pilha:

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

Eu estou chamando, através da reflexão, um método que pode causar uma exceção. Como posso passar a exceção para o meu chamador sem o wrapper reflexo coloca em torno dele?
Estou rethrowing a InnerException, mas isso destrói o rastreamento de pilha.
Exemplo 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;
    }
}

Ainda mais reflexão ...

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

Lembre-se de que isso pode ocorrer a qualquer momento, pois os campos privados não fazem parte da API. Veja mais discussões sobre o Mono bugzilla .


Gente, você é legal .. Eu vou ser um necromante em breve.

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

    }

Ninguém explicou a diferença entre ExceptionDispatchInfo.Capture( ex ).Throw() e um throw simples, então aqui está.

A maneira completa de retrabalhar uma exceção capturada é usar ExceptionDispatchInfo.Capture( ex ).Throw() (disponível somente em .Net 4.5).

Abaixo estão os casos necessários para testar isso:

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

O caso 1 e o caso 2 fornecerão um rastreamento de pilha em que o número da linha do código-fonte do método CallingMethod é o número da linha da throw new Exception( "TEST" ) .

No entanto, o caso 3 lhe dará um rastreamento de pilha em que o número da linha do código-fonte para o método CallingMethod é o número da linha da chamada em CallingMethod . Isso significa que se a throw new Exception( "TEST" ) do throw new Exception( "TEST" ) estiver rodeada por outras operações, você não tem idéia de qual número de linha a exceção foi realmente lançada.

O caso 4 é semelhante ao caso 2 porque o número da linha da exceção original é preservado, mas não é um real relançamento porque altera o tipo da exceção original.


Primeiro: não perca o TargetInvocationException - é uma informação valiosa quando você quer depurar as coisas.
Segundo: Envolva o TIE como InnerException em seu próprio tipo de exceção e coloque uma propriedade OriginalException que vincula ao que você precisa (e mantenha o callstack inteiro intacto).
Terceiro: Deixe o TIE sair do seu método.


Um outro código de amostra que usa serialização / desserialização de exceção. Não requer que o tipo de exceção real seja serializável. Também usa somente 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 });
    }




exception