345

我正在通过反射调用一个可能导致异常的方法。如何在没有包装反射的情况下将异常传递给调用者?
我正在重新抛出 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;
    }
}
4

10 回答 10

542

.NET 4.5中现在有这个ExceptionDispatchInfo类。

这使您可以在不更改堆栈跟踪的情况下捕获异常并重新抛出它:

using ExceptionDispatchInfo = 
    System.Runtime.ExceptionServices.ExceptionDispatchInfo;

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

这适用于任何异常,而不仅仅是AggregateException.

它是由于awaitC# 语言特性而引入的,该特性从实例中解开内部异常,AggregateException以使异步语言特性更像同步语言特性。

于 2013-06-13T15:42:46.940 回答
87

可以重新抛出之前保留堆栈跟踪而不进行反射:

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 ;
}
于 2010-01-18T10:42:22.577 回答
35

我认为你最好的选择就是把它放在你的 catch 块中:

throw;

然后稍后提取内部异常。

于 2008-09-11T19:20:00.400 回答
26

ExceptionDispatchInfo.Capture( ex ).Throw()没有人解释和 plain之间的区别throw,所以在这里。

重新抛出捕获的异常的完整方法是使用ExceptionDispatchInfo.Capture( ex ).Throw()(仅适用于 .Net 4.5)。

以下是对此进行测试所需的案例:

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

案例 1 和案例 2 将为您提供堆栈跟踪,其中CallingMethod方法的源代码行号是该行的行号throw new Exception( "TEST" )

但是,案例 3 将为您提供堆栈跟踪,其中方法的源代码行号是调用CallingMethod的行号。throw这意味着如果该throw new Exception( "TEST" )行被其他操作包围,您将不知道实际抛出异常的行号。

情况 4 与情况 2 类似,因为保留了原始异常的行号,但不是真正的重新抛出,因为它改变了原始异常的类型。

于 2016-11-14T10:28:48.990 回答
14
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 );
    }
}

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

于 2009-11-02T20:39:15.773 回答
11

根据 Paul Turners 的回答,我做了一个扩展方法

    public static Exception Capture(this Exception ex)
    {
        ExceptionDispatchInfo.Capture(ex).Throw();
        return ex;
    }

return ex从来没有达到过,但优点是我可以用作throw ex.Capture()一个衬里,这样编译器就不会引发not all code paths return a value错误。

    public static object InvokeEx(this MethodInfo method, object obj, object[] parameters)
    {
        {
            return method.Invoke(obj, parameters);
        }
        catch (TargetInvocationException ex) when (ex.InnerException != null)
        {
            throw ex.InnerException.Capture();
        }
    }
于 2019-07-16T08:09:40.500 回答
10

更多的反思...

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的进一步讨论。

于 2008-09-11T19:20:25.967 回答
10

第一:不要丢失 TargetInvocationException - 当您想要调试事物时,它是有价值的信息。
第二:将 TIE 作为 InnerException 包装在您自己的异常类型中,并放置一个 OriginalException 属性,该属性链接到您需要的内容(并保持整个调用堆栈完好无损)。
第三:让 TIE 从你的方法中消失。

于 2008-09-11T19:22:57.770 回答
5

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

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

    }
于 2010-01-02T17:48:10.210 回答
3

另一个使用异常序列化/反序列化的示例代码。它不需要实际的异常类型是可序列化的。它也只使用公共/受保护的方法。

    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 });
    }
于 2012-04-03T08:19:24.807 回答