在C#中,如何在不丢失堆栈跟踪的情况下重新抛出InnerException?

时间:2020-03-05 18:51:44  来源:igfitidea点击:

我通过反射调用可能导致异常的方法。在没有包装反射的情况下,如何将异常传递给调用者?我抛出了InnerException,但是这破坏了堆栈跟踪。示例代码:

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

解决方案

回答

我认为我们最好的选择是将其放在catch块中:

throw;

然后稍后提取内部异常。

回答

甚至更多的反思...

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

请记住,这可能随时会中断,因为私有字段不是API的一部分。请参阅有关Mono bugzilla的进一步讨论。

回答

首先:不要丢失TargetInvocationException,这是当我们要调试事物时的宝贵信息。
第二:将TIE作为InnerException包裹在我们自己的异常类型中,并放置一个可链接到所需内容的OriginalException属性(并保持整个调用堆栈不变)。
第三:让TIE冒出方法。

回答

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

在抛出异常之前调用扩展方法,它将保留原始堆栈跟踪。

回答

伙计们,你很酷..我很快就会成为一个死灵法师。

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

    }

回答

可以在重新反射之前保留堆栈跟踪,而无需进行反射:

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
}

与通过缓存的委托调用InternalPreserveStackTrace相比,这浪费了很多周期,但具有仅依赖于公共功能的优点。这是堆栈跟踪保留功能的两种常见用法模式:

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