关于.net:如何重新抛出InnerException而不会丢失C#中的堆栈跟踪?

关于.net:如何重新抛出InnerException而不会丢失C#中的堆栈跟踪?

How to rethrow InnerException without losing stack trace in C#?

我正在通过反射调用可能导致异常的方法。如果没有包装反射,如何将异常传递给调用方?我正在重新处理innerException,但这会破坏堆栈跟踪。示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
    }
}

在.NET 4.5中,现在有了ExceptionDispatchInfo类。

这允许您捕获一个异常并在不更改堆栈跟踪的情况下重新抛出它:

1
2
3
4
5
6
7
8
try
{
    task.Wait();
}
catch(AggregateException ex)
{
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}

这适用于任何例外情况,而不仅仅是AggregateException

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


可以在不反射的情况下重新刷新之前保留堆栈跟踪:

1
2
3
4
5
6
7
8
9
10
11
12
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相比,这浪费了很多周期,但具有仅依赖公共功能的优势。以下是堆栈跟踪保留函数的几种常见使用模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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 ;
}


我想你最好的办法就是把这个放在你的拦网里:

1
throw;

稍后提取神经感觉。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 );
    }
}

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


更多的思考…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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的讨论。


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

重新引发捕获的异常的完整方法是使用ExceptionDispatchInfo.Capture( ex ).Throw()(仅可从.NET 4.5获得)。

下面是测试这一点的必要案例:

1。

1
2
3
4
5
6
7
8
9
10
11
void CallingMethod()
{
    //try
    {
        throw new Exception("TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2。

1
2
3
4
5
6
7
8
9
10
11
12
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.
    }
}

三。

1
2
3
4
5
6
7
8
9
10
11
void CallingMethod()
{
    try
    {
        throw new Exception("TEST" );
    }
    catch
    {
        throw;
    }
}

4。

1
2
3
4
5
6
7
8
9
10
11
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类似,因为保留了原始异常的行号,但由于它更改了原始异常的类型,因此不是真正的重新引发。


第一:不要丢失TargetInvocationException——当您想要调试东西时,它是很有价值的信息。第二:在您自己的异常类型中将领带包装为innerException,并放置一个链接到所需内容的originalException属性(并保持整个调用堆栈完整)。第三:让你的方法失效。


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

1
2
3
4
5
6
7
8
9
10
11
12
    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))();

    }

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

1
2
3
4
5
6
7
8
9
    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 });
    }

推荐阅读

    浏览器调用linux命令?

    浏览器调用linux命令?,系统,信息,人工智能,软件,数据,首次,地址,代码,咨询,

    py调用linux的命令?

    py调用linux的命令?,系统,代码,状态,环境,标准,工具,命令,文件,脚本,终端,lin

    linux系统命令调用?

    linux系统命令调用?,系统,单位,工具,工作,管理,地址,权威,密码,电脑,信息,怎

    linux调用上一条命令?

    linux调用上一条命令?,系统,命令,一致,数字,名称,网上,电脑,目录,空格,终端,l

    linux命令行调用程序?

    linux命令行调用程序?,工具,环境,代码,初级,工程,系统,网上,服务,管理,发行,l

    脚本调用linux命令?

    脚本调用linux命令?,代码,系统,工作,底部,脚本,位置,环境,行用,官网,标准,typ

    linux做路由跟踪命令?

    linux做路由跟踪命令?,网络,系统,地址,设备,信息,电脑,工作,命令,服务,全球,l

    调用函数命令linux?

    调用函数命令linux?,系统,管理,网络,通用,统一,观察,地址,代码,设备,地方,怎

    linux开启命令跟踪?

    linux开启命令跟踪?,系统,信息,软件,对比,工具,发行,通信,实时,状态,最新,如

    linux内核总调用命令?

    linux内核总调用命令?,工作,地址,系统,信息,管理,策略,命令,目录,时间,基础,

    linux保留堆栈命令?

    linux保留堆栈命令?,地址,工作,系统,信息,管理,命令,目录,代码,名称,连续,lin

    linux跟踪路由器命令?

    linux跟踪路由器命令?,网络,地址,系统,信息,情况,工具,时间,通信,命令,管理

    linux编程调用命令?

    linux编程调用命令?,系统,标准,管理,工作,基础知识,情况,环境,设备,基础,首

    linux命令窗口调用?

    linux命令窗口调用?,系统,工具,首页,终端,命令,数据,盘中,代码,密码,快捷键,

    网络跟踪命令linux?

    网络跟踪命令linux?,网络,地址,工具,系统,时间,数据,下行,信息,设备,基础,lin

    linux服务器调用命令?

    linux服务器调用命令?,系统,地址,设备,标准,工作,密码,中心,盘中,网络,软件,

    linuxc调用命令框?

    linuxc调用命令框?,系统,工作,标准,情况,设备,环境,命令,函数,语言,程序,如何

    linux系统调用命令行?

    linux系统调用命令行?,系统,网络,代码,密码,位置,工作,通用,平台,电脑,环境,

    linux跟踪网关命令?

    linux跟踪网关命令?,地址,网络,系统,信息,电脑,时间,设备,名字,命令,数据,在l

    批量调用linux命令?

    批量调用linux命令?,工具,下来,软件,系统,密码,地址,时间,认证,向日葵,文件,l