10

可能重复:
重新抛出错误的堆栈跟踪

人们普遍认为,在 .NETthrow;中不会重置堆栈跟踪,但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:line 13 中的 ConsoleApplication2.Program.Main(String[] args)

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

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

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

请注意,这已经被观察到,但一般没有回答。


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

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

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

请注意:最后的行号仍然会改变

4

2 回答 2

5

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

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

[MethodImpl(MethodImplOptions.NoInlining)]
public void Throw()
{
    var x = (string)(object)1;
}
于 2012-09-11T15:57:40.447 回答
4

正如 Darin 已经指出的那样,减少的堆栈跟踪是由于方法内联。但是,堆栈跟踪中可用的线参考点也不相等。

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

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

.NET 异常 - throw ex 是邪恶的,但 throw 不是那么无辜


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

它还会在重新抛出的方法中更改堆栈跟踪中的行号(因为重新抛出成为该方法中的抛出位置)。

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

因此,即使在使用时保留堆栈跟踪throw而不是throw e原始抛出站点也会丢失,除非您包装并抛出新异常。

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

于 2012-09-11T16:09:42.050 回答