CLR对'throw'做了什么?

时间:2010-08-19 23:15:17

标签: .net clr cil

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

因此产生了:

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

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

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

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

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

但我发现了一些奇怪的事情:

var exception = new Exception();
throw exception;

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

以上等同于以下IL:

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

在我看来,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());
}

1 个答案:

答案 0 :(得分:5)

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

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

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

另一个编辑

只是想我会提供一些额外的信息&amp;链接。基本上,CLR仅在抛出异常时才在异常对象中构建堆栈跟踪。这已在MSDN中提及 - 引自MSDN:

  

公共语言运行库(CLR)每当更新堆栈跟踪时   应用程序代码中抛出异常(通过使用throw关键字)。   如果异常是在一个不同于的方法中重新抛出的   最初抛出它的方法,堆栈跟踪包含两者   方法中最初抛出异常的位置,   以及重新抛出异常的方法中的位置。如果   抛出异常,然后在同一个方法中重新抛出异常   堆栈跟踪仅包含异常所在的位置   rethrown并且不包括异常的位置   最初抛出

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

在某些相关说明(但有点偏离主题)上,请参阅this excellent article(带有示例代码)作者构建备用堆栈跟踪信息(基本上,他从非标准位置查找调试信息)。