CLR对'throw'做了什么?

eco*_*fey 3 .net clr cil

当我正在开展一个项目时,我心里想着"嗯,记录一条消息会非常方便,然后用相同的消息抛出异常".由于这会让我保持"特殊情况下的例外"原则,但仍然要确保我们记录有关系统出错的详细信息.

因此,这导致:

public static class LogAndThrow
{
    public static void Message<TException>(string message) where TException : Exception
    {
        // Log message here

        var constructor = 
            typeof(TException).GetConstructor(new[] { typeof(string) });

        throw (TException)constructor.Invoke(new[] { message });
    }
}
Run Code Online (Sandbox Code Playgroud)

当然它有点粗糙(我把这篇文章剪下来了),但它确实有效.

然而,作为通过反射构建异常的人的类型,我很恼火,堆栈跟踪将被"玷污"LogAndThrow.Message()行.

所以我开始解决这个问题:-)

我能够用一些序列化和其他技巧替换堆栈跟踪,所有这些都非常愚蠢,而且非常蛮力.但我想解决这个问题只是因为.

但我发现了一些好奇的东西:

var exception = new Exception();
throw exception;
Run Code Online (Sandbox Code Playgroud)

在创建该异常之后,但在抛出之前,唯一设置的是Message.堆栈跟踪等是空的.

以上等同于以下IL:

.locals init (
    [0] class [mscorlib]System.Exception exception)
nop 
newobj instance void [mscorlib]System.Exception::.ctor()
stloc.0 
ldloc.0 
throw 
Run Code Online (Sandbox Code Playgroud)

在我看来,IL'for throw'正在做的不仅仅是采用该引用并将其向上移动.

当达到IL'throw'时,有没有人知道运行时对堆栈的异常做了什么?

我们在下面用来改变堆栈的技巧与我认为的"魔法"有关:

这段代码很可怕也很错误.更多的科学实验比任何应该投入生产的科学实验都要严格

var e = new Exception("message here");
try
{
     throw e;
}
finally
{
    // Get the private file _stackTraceString with reflection
    field.SetValue(e, new StackTrace(1).ToString());
}
Run Code Online (Sandbox Code Playgroud)

Vin*_*ayC 5

为什么不能修改静态方法以返回异常对象并稍后抛出.例如

// Do something
...
// Found error condition, need to throw an exception
if (error condition)
{
  throw LogAndThrow.Message("Message goes here");
}
Run Code Online (Sandbox Code Playgroud)

编辑:AFAIK,无法修改堆栈跟踪.有一些方法可以保留原始堆栈跟踪,同时重新抛出异常 - 请参阅此文章.

另一个编辑:

只是想我会提供一些额外的信息和链接.基本上,CLR仅在抛出异常时才在异常对象中构建堆栈跟踪.这在MSDN上已经提到了- 来自MSDN:

只要在应用程序代码中抛出异常(通过使用throw关键字),公共语言运行库(CLR)就会更新堆栈跟踪.如果在与最初抛出异常的方法不同的方法中重新抛出异常,则堆栈跟踪包含最初抛出异常的方法中的位置,以及重新抛出异常的方法中的位置.如果在同一方法中抛出异常并稍后重新抛出异常,则堆栈跟踪仅包含重新抛出异常的位置,并且不包括最初抛出异常的位置

这里也提到这一点(其中作者提到CLR在遇到托管代码中的异常时会执行堆栈遍历).

在一些相关的说明(但有点偏离主题),请参阅这篇优秀的文章(带有示例代码),其中作者构造备用堆栈跟踪信息(基本上,他从非标准位置查找调试信息).