扔;据说不会重置堆栈跟踪,但它确实在某些情况下

时间:2012-09-11 15:28:18

标签: c# .net

  

可能重复:
  incorrect stacktrace by rethrow

通常认为在.NET中throw;不会重置堆栈跟踪,而throw ex;会重置堆栈跟踪。

但是,在这个简单的程序中,我得到了不同的行号:

void Main()
{
    try
    {
        try
        {
            Wrapper(); // line 13
        }
        catch(Exception e)
        {
            Console.WriteLine(e.ToString());
            throw; // line 18
        }
    }
    catch(Exception e)
    {
          Console.WriteLine(e.ToString());
    }
}

public void Wrapper()
{
    Throw(); // line 28
}

public void Throw()
{
    var x = (string)(object)1; // line 33
}

输出结果为:

  

System.InvalidCastException:无法将类型为“System.Int32”的对象强制转换为“System.String”。      在C:\ long-path \ Program.cs中的ConsoleApplication2.Program.Main(String [] args):第13行

     

System.InvalidCastException:无法将类型为“System.Int32”的对象强制转换为“System.String”。      在C:\ long-path \ Program.cs中的ConsoleApplication2.Program.Main(String [] args):第18行

注意:第一个堆栈跟踪包含第13行,第二个包含第18行。此外,第13行和第18行都不是转换实际发生的行。

我现在的问题是:throw;在什么情况下会改变堆栈跟踪,哪种情况不会改变堆栈跟踪?

请注意,这已经been observed,但一般都没有回答。


更新
我在调试模式下运行上面的代码,它产生了这个:

  

System.InvalidCastException:无法将类型为“System.Int32”的对象强制转换为“System.String”。      在C:\ long-path \ Program.cs:第33行的ConsoleApplication2.Program.Throw()中      在C:\ long-path \ Program.cs:第28行的ConsoleApplication2.Program.Wrapper()中      在C:\ long-path \ Program.cs中的ConsoleApplication2.Program.Main(String [] args):第13行

     

System.InvalidCastException:无法将类型为“System.Int32”的对象强制转换为“System.String”。      在C:\ long-path \ Program.cs:第33行的ConsoleApplication2.Program.Throw()中      在C:\ long-path \ Program.cs:第28行的ConsoleApplication2.Program.Wrapper()中      在C:\ long-path \ Program.cs中的ConsoleApplication2.Program.Main(String [] args):第18行

请注意:最后一个行号仍会改变

2 个答案:

答案 0 :(得分:5)

发生这种情况的原因是在Release模式下运行时的方法内联。如果您不希望在发布模式下内联WrapperThrow方法,则可以使用[MethodImpl]属性修饰它们:

[MethodImpl(MethodImplOptions.NoInlining)]
public void Wrapper()
{
    Throw();
}

[MethodImpl(MethodImplOptions.NoInlining)]
public void Throw()
{
    var x = (string)(object)1;
}

答案 1 :(得分:4)

正如Darin已经指出减少的堆栈跟踪是由于方法内联。但是,堆栈跟踪中可用的行引用也不相等。

我不知道这背后的解释,但是有一种方法可以在重新抛出异常时保留所有堆栈跟踪信息。您需要抛出一个新异常并将您捕获的异常作为内部异常传递。使用这种方法,合并的堆栈跟踪将包含异常的起始点以及重新抛出异常的点。

我谈到了这一点,并在以下博客文章中提供了有关重新抛出异常的不同方法的完整示例:

.NET Exceptions – throw ex is evil but throw is not that innocent


您的评论促使我进行了快速研究,但我能找到的最好的是Jonathan de Halleux在关于catch and rethrow的博客文章中发表的评论:

  

它还改变了方法中stacktrace中的行号   重新抛出(因为重新抛出成为该方法中抛出的位置)。

这可以进一步阐述,但它指出了这样一个事实,即每个方法可能会跟踪将用于获取线路信息的投掷站点,并且重新抛出会导致它被覆盖。

因此,即使使用throw而不是throw e保留堆栈跟踪,原始投掷站点也将丢失,除非您换行并抛出新的异常。

<子> 其他要尝试的事情;由于SO不允许直接发送消息而上述评论是由Peli发出的,因此您可以尝试使用pex标记此问题以引起他的注意并让他跟进该评论。 :)