【问题标题】:throw; is said to not reset stack trace, but it does in certain circumstances [duplicate]扔;据说不会重置堆栈跟踪,但在某些情况下会这样做[重复]
【发布时间】:2012-09-11 15:28:18
【问题描述】:

可能重复:
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: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; 在什么情况下会更改堆栈跟踪,在什么情况下不会更改堆栈跟踪?

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


更新:
我在调试模式下运行了上面的代码,结果如下:

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

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

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

【问题讨论】:

  • 如果异常中详述的行号与您提供的代码的 sn-p 相关联会有所帮助...

标签: c# .net


【解决方案1】:

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

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

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

【讨论】:

  • 好的,这就解释了为什么第一个 catch 中的行号不是演员表的行号。它仍然无法解释为什么throw; 会更改堆栈跟踪中的行号。
  • 我正要按照这些思路说些什么,所以只是补充一下:在调试模式下运行代码会产生完整的调用堆栈,它们都源自“Throw”方法。
  • @DanielHilgarth 您看到的行号是重新抛出异常的行。在发布版本中,这就是您所获得的所有内容,因为其余部分已内联。如果你想要异常的来源,你需要完整的堆栈。
  • 如果最后的行号没有改变,你将无法判断异常被重新抛出。 throw e;throw; 的区别在于前者会在重新抛出点之前清除堆栈,而后者将保留它。在这两种情况下,重新抛出都会添加到堆栈中,就像它应该的那样。
  • @BrianRasmussen:throw; 仍然丢失了重要信息。如果try 中的内容不仅仅是对方法的一次调用,而是类似这样的内容:int.Parse(x1); int.Parse(x2);(当然是在单独的行上)。在throw 之后,您将不知道两者中的哪一个引发了异常。
【解决方案2】:

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

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

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

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


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

它还更改了堆栈跟踪中的行号 rethrows(因为 rethrow 成为该方法中 throw 的位置)。

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

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

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

【讨论】:

  • 谢谢。我知道包装它会保留完整的堆栈跟踪。我问这个问题是为了为以后的帖子提供参考,人们说只使用throw;“因为它保留了堆栈跟踪”,而实际上它并不总是这样做。
  • 我用互联网上的更多信息更新了这个问题,你可能已经看到了,但可能对参考有用。但是,它仍然没有完全解释这个主题。
猜你喜欢
  • 2010-10-18
  • 1970-01-01
  • 2012-08-09
  • 1970-01-01
  • 2021-08-21
  • 2017-05-14
  • 2010-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多