什么会导致 throw 重置调用堆栈(我使用的是“ throw”,而不是“ throw ex”)
我一直认为“扔”和“扔前”之间的区别 单独抛出并没有重置异常的堆栈跟踪。
不幸的是,这不是我遇到的行为;这是一个重现我的问题的简单示例:
using System;
using System.Text;
namespace testthrow2
{
class Program
{
static void Main(string[] args)
{
try
{
try
{
throw new Exception("line 14");
}
catch (Exception)
{
throw; // line 18
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.ReadLine();
}
}
}
我希望这段代码从第 14 行开始打印一个调用堆栈;然而,调用堆栈从第 18 行开始。当然,在示例中这没什么大不了的,但在我的实际应用程序中,丢失初始错误信息是相当痛苦的。
我错过了一些明显的东西吗?有没有另一种方法可以实现我想要的(即重新抛出异常而不丢失堆栈信息?)
我正在使用 .net 3.5
I've always thought the difference between "throw" and "throw ex" was that throw alone wasn't resetting the stacktrace of the exception.
Unfortunately, that's not the behavior I'm experiencing ; here is a simple sample reproducing my issue :
using System;
using System.Text;
namespace testthrow2
{
class Program
{
static void Main(string[] args)
{
try
{
try
{
throw new Exception("line 14");
}
catch (Exception)
{
throw; // line 18
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.ReadLine();
}
}
}
I would expect this code to print a callstack starting at line 14 ; however the callstack starts at line 18. Of course it's no big deal in the sample, but in my real life application, losing the initial error information is quite painful.
Am I missing something obvious? Is there another way to achieve what I want (ie re throwing an exception without losing the stack information?)
I'm using .net 3.5
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您应该阅读这篇文章:
简而言之,
抛出
通常保留原始抛出异常的堆栈跟踪,但前提是该异常没有发生不会发生在当前堆栈帧(即方法)中。您可以使用一种方法
PreserveStackTrace
(如该博客文章所示)来保留原始堆栈跟踪,如下所示:但我通常的解决方案是要么简单地不捕获并重新抛出这样的异常(除非绝对必要),或者只是使用
InnerException
属性始终抛出新异常来传播原始异常:You should read this article:
In short,
throw
usually preserves the stack trace of the original thrown exception, but only if the exception didn't occur in the current stack frame (i.e. method).There is a method
PreserveStackTrace
(shown in that blog article) that you use that preserves the original stack trace like this:But my usual solution is to either simply to not catch and re throw exceptions like this (unless absolutely necessary), or just to always throw new exceptions using the
InnerException
property to propagate the original exception:问题是 Windows 正在重置堆栈的起点。 CLR 的行为符合预期 — 这只是主机操作系统的异常处理支持的限制。问题是每个方法调用只能有一个堆栈帧。
您可以将异常处理例程提取到一个单独的“帮助程序”方法中,该方法可以解决 Windows SEH 施加的限制,但我认为这不一定是一个好主意。
在不丢失堆栈信息的情况下重新抛出异常的正确方法是抛出一个新异常并将原始捕获的异常作为内部异常。
很难想象在很多情况下您确实需要这样做。如果您不处理异常,而只是捕获它并重新抛出它,那么您可能一开始就不应该捕获它。
The problem is that Windows is resetting the stack's starting point. The CLR is behaving as expected—this is just a limitation of the host operating system's exception handling support. The problem is that there can only be one stack frame per method call.
You could extract your exception handling routines into a separate "helper" method, which would work around the limitations imposed by Windows's SEH, but I don't think that's necessarily a good idea.
The proper way to rethrow an exception without losing the stack information is to throw a new exception and include the original, caught exception as the inner exception.
It's difficult to imagine very many cases where you'd really need to do this. If you're not handling the exception, and simply catching it to rethrow it, you probably shouldn't be catching it in the first place.
正常的重新抛出会保留堆栈跟踪中的所有内容,但如果当前方法位于堆栈跟踪中,则行号将被覆盖。这是令人讨厌的行为。在 C# 中,如果需要在异常情况下做某事但不关心异常是什么,可以使用以下模式:
在很多情况下该模式非常有用;最常见的是一个应该返回新 IDisposable 的函数。如果函数不返回,则必须清理一次性对象。 请注意,上述“try”块中的任何“return”语句都必须将 ok 设置为 true。
在 vb.net 中,可以使用一种功能上更好一点的模式,尽管代码中的一个地方有点令人讨厌,使用该模式:
长命名的函数应该以明显的方式实现。此模式的优点是使异常可用于代码。虽然在处理但不捕获的情况下通常不需要这样做,但在一种情况下它可能非常有价值:如果清理例程引发异常。通常,如果清理例程引发异常,则任何待处理的异常都将丢失。但是,使用上述模式,可以将挂起的异常包装在清理异常中。
上述代码有一个有趣的注释:异常可能会到达“Catch When”,但 Try 语句仍能正常完成。实际上并不清楚在这种情况下会发生什么,但有一点是明确的:Finally 语句不应表现得好像异常处于待处理状态。清除 PendingException 将使异常消失后,代码的行为就像从未发生过一样。另一种方法是包装并重新抛出已知已发生的异常,因为这种情况几乎肯定表明内部异常处理代码存在问题。
The normal rethrow preserves everything on the stack trace except that if the present method is in the stack trace, the line number will get overwritten. This is annoying behavior. In C# if one needs to do something in the exceptional case but doesn't care what the exception is, one can use the pattern:
There are a number where that pattern is very helpful; the most common would be a function which is supposed to return a new IDisposable. If the function isn't going to return, the disposable object must get cleaned up. Note that any "return" statements within the above "try" block must set ok to true.
In vb.net, it's possible to use a pattern which is functionally a little nicer, though one spot in the code is a little icky, with the pattern:
The long-named function should be implemented in the obvious fashion. This pattern has the advantage of making the exception available to the code. While that isn't often needed in handle-but-don't-catch situations, there's one situation where it can be invaluable: if a cleanup routine throws an exception. Normally, if a cleanup routine throws an exception, any pending exception will be lost. With the above pattern, however, it's possible to wrap the pending exception within the cleanup exception.
One interesting note with the above code: it's possible for an exception to reach the "Catch When" and yet for the Try statement to complete normally. It's really not clear what should happen in that circumstance, but one thing that is clear is that the Finally statement should not act as though an exception is pending. Clearing PendingException will make it so that if an exception vanishes, the code will behave as though it never happened. An alternative would be to wrap and rethrow an exception which is known to have occurred, since that situation almost certainly indicates something wrong with the inner exception-handling code.