重新抛出错误的堆栈跟踪
我用“throw;”重新抛出异常,但堆栈跟踪不正确:
static void Main(string[] args) {
try {
try {
throw new Exception("Test"); //Line 12
}
catch (Exception ex) {
throw; //Line 15
}
}
catch (Exception ex) {
System.Diagnostics.Debug.Write(ex.ToString());
}
Console.ReadKey();
}
正确的堆栈跟踪应该是:
System.Exception:测试 在 Program.cs 中的 ConsoleApplication1.Program.Main(String[] args):第 12 行
但我得到:
System.Exception:测试 在 Program.cs 中的 ConsoleApplication1.Program.Main(String[] args):第 15 行
但第 15 行是“throw;”的位置。我已经用 .NET 3.5 对此进行了测试。
I rethrow an exception with "throw;", but the stacktrace is incorrect:
static void Main(string[] args) {
try {
try {
throw new Exception("Test"); //Line 12
}
catch (Exception ex) {
throw; //Line 15
}
}
catch (Exception ex) {
System.Diagnostics.Debug.Write(ex.ToString());
}
Console.ReadKey();
}
The right stacktrace should be:
System.Exception: Test at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 12
But I get:
System.Exception: Test at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 15
But line 15 is the position of the "throw;". I have tested this with .NET 3.5.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
您抛出一个新异常,并将原始异常作为内部异常包含在内。
您对丑陋的看法是错误的,但对其他两点的看法是正确的。经验法则是:除非你打算用它做一些事情,比如包装它、修改它、吞下它或记录它,否则不要捕获它。如果您决定
抓住
然后再次抛出
,请确保您正在用它做一些事情,否则就让它冒泡。您可能还想简单地放置一个 catch,以便可以在 catch 中设置断点,但 Visual Studio 调试器有足够的选项使这种做法变得不必要,请尝试使用第一次机会异常或条件断点。
You throw a new exception, and include the original exception as the inner exception.
You are wrong about the ugly but right about the other two points. The rule of thumb is: don't catch unless you are going to do something with it, like wrap it, modify it, swallow it, or log it. If you decide to
catch
and thenthrow
again, make sure you are doing something with it, otherwise just let it bubble up.You may also be tempted to put a catch simply so you can breakpoint within the catch, but the Visual Studio debugger has enough options to make that practice unnecessary, try using first chance exceptions or conditional breakpoints instead.
编辑/替换
行为实际上有所不同,但很微妙。至于为什么行为不同,我需要听从 CLR 专家的意见。
编辑:AlexD的回答< /a> 似乎表明这是设计使然。
在捕获异常的同一个方法中抛出异常会使情况有点混乱,所以让我们从另一个方法抛出异常:
If
throw; 时,调用堆栈为(用代码替换行号):
如果使用
throw ex;
,则调用堆栈为:如果未捕获异常,则调用堆栈为:
在 .NET 中测试4 / VS 2010
Edit/Replace
The behavior is actually different, but subtilely so. As for why the behavior if different, I'll need to defer to a CLR expert.
EDIT: AlexD's answer seems to indicate that this is by design.
Throwing the exception in the same method that catches it confuses the situation a little, so let's throw an exception from another method:
If
throw;
is used, the callstack is (line numbers replaced with code):If
throw ex;
is used, the callstack is:If exception is not caught, the callstack is:
Tested in .NET 4 / VS 2010
这里有一个重复的问题。
据我了解 - 扔;被编译成 'rethrow' MSIL 指令,它修改了最后一帧堆栈跟踪。
我希望它保留原始堆栈跟踪并在重新抛出的位置添加行,但显然每个方法调用只能有一个堆栈帧。
结论:避免使用 throw;并在重新抛出时将您的异常包装在一个新的异常中 - 这并不难看,这是最佳实践。
There is a duplicate question here.
As I understand it - throw; is compiled into 'rethrow' MSIL instruction and it modifies the last frame of the stack-trace.
I would expect it to keep the original stack-trace and add the line where it has been re-thrown, but apparently there can only be one stack frame per method call.
Conclusion: avoid using throw; and wrap your exception in a new one on re-throwing - it's not ugly, it's best practice.
保留堆栈跟踪
您可以使用以下代码示例
:输出将类似于:
You can preserve stack trace using
Here is code sample:
The output will be something like:
好的,.NET Framework 中似乎存在一个错误,如果您抛出异常,并在同一个方法中重新抛出它,则原始行号会丢失(它将是该方法的最后一行)。
幸运的是,一个名叫 Fabrice MARGUERIE 的聪明人发现了 此错误的解决方案。下面是我的版本,您可以在 这个 .NET Fiddle 中进行测试。
现在像往常一样捕获异常,但不是抛出;只需调用此方法,瞧,原始行号将被保留!
OK, there seems to be a bug in the .NET Framework, if you throw an exception, and rethrow it in the same method, the original line number is lost (it will be the last line of the method).
Fortunatelly, a clever guy named Fabrice MARGUERIE found a solution to this bug. Below is my version, which you can test in this .NET Fiddle.
Now catch the exception as usually, but instead of throw; just call this method, and voila, the original line number will be preserved!
不确定这是否是设计使然,但我认为一直都是这样。
如果原始的 throw new Exception 位于单独的方法中,则 throw 的结果应包含原始方法名称和行号,然后是 main 中重新抛出异常的行号。
如果您使用 throw ex,那么结果将只是 main 中重新抛出异常的行。
换句话说, throw ex 会丢失所有堆栈跟踪,而 throw 则保留堆栈跟踪历史(即较低级别方法的详细信息)。但是,如果您的异常是通过与重新抛出相同的方法生成的,那么您可能会丢失一些信息。
注意。如果您编写一个非常简单且小型的测试程序,框架有时可以进行优化并将方法更改为内联代码,这意味着结果可能与“真实”程序不同。
Not sure whether this is by design, but I think it has always been like that.
If the original throw new Exception is in a separate method, then the result for throw should have the original method name and line number and then the line number in main where the exception is re-thrown.
If you use throw ex, then the result will just be the line in main where the exception is rethrow.
In other words, throw ex loses all the stacktrace, whereas throw preserves the stack trace history (ie details of the lower level methods). But if your exception is generated by the same method as your rethrow, then you can lose some information.
NB. If you write a very simple and small test program, the Framework can sometimes optimise things and change a method to be inline code which means the results may differ from a 'real' program.
您想要正确的线路号码吗?每个方法只需使用一次 try/catch 即可。在系统中,嗯......只是在UI层,而不是在逻辑或数据访问中,这非常烦人,因为如果你需要数据库事务,那么,它们不应该在UI层,而且你不会有正确的行号,但如果您不需要它们,无论在 catch 中是否有异常,都不要重新抛出...
5 分钟示例代码:
Menu File -> 新建项目,放置三个按钮,并在每个按钮中调用以下代码:
现在,创建一个新类:
Run...第一个按钮很漂亮!
Do you want your right line number? Just use one try/catch per method. In systems, well... just in the UI layer, not in logic or data access, this is very annoying, because if you need database transactions, well, they shouldn't be in the UI layer, and you won't have the right line number, but if you don't need them, don't rethrow with nor without an exception in catch...
5 minutes sample code:
Menu File -> New Project, place three buttons, and call the following code in each one:
Now, create a new Class:
Run... the first button is beautiful!
我认为这不是堆栈跟踪更改的情况,更多的是与确定堆栈跟踪的行号的方式有关。在 Visual Studio 2010 中进行尝试,其行为与您在 MSDN 文档中所期望的类似:“throw ex;”从该语句“throw;”开始重建堆栈跟踪保留堆栈跟踪,除了重新抛出异常的位置之外,行号是重新抛出的位置,而不是异常所经过的调用。
所以用“投掷;”方法调用树保持不变,但行号可能会改变。
我已经遇到过几次这种情况,这可能是设计使然,只是没有完整记录。我可以理解为什么他们可能这样做,因为知道重新抛出位置非常有用,并且如果您的方法足够简单,那么原始来源通常是显而易见的。
正如许多其他人所说,通常最好不要捕获异常,除非您确实必须这样做,和/或您要在那时处理它。
有趣的旁注:Visual Studio 2010 甚至不允许我构建问题中提供的代码,因为它在编译时出现除以零错误。
I think this is less a case of stack trace changing and more to do with the way the line number for the stack trace is determined. Trying it out in Visual Studio 2010, the behaviour is similar to what you would expect from the MSDN documentation: "throw ex;" rebuilds the stack trace from the point of this statement, "throw;" leaves the stack trace as it as, except that where ever the exception is rethrown, the line number is the location of the rethrow and not the call the exception came through.
So with "throw;" the method call tree is left unaltered, but the line numbers may change.
I've come across this a few times, and it may be by design and just not documented fully. I can understand why they may have done this as the rethrow location is very useful to know, and if your methods are simple enough the original source would usually be obvious anyway.
As many other people have said, it usually best to not catch the exception unless you really have to, and/or you are going to deal with it at that point.
Interesting side note: Visual Studio 2010 won't even let me build the code as presented in the question as it picks up the divide by zero error at compile time.
这是因为您从第 12 行捕获了异常,并在第 15 行重新抛出了它,因此堆栈跟踪将其视为现金,即从那里抛出了
Exception
。为了更好地处理异常,您应该简单地使用
try...finally
,并让未处理的Exception
冒泡。That is because you catched the
Exception
from Line 12 and have rethrown it on Line 15, so the Stack Trace takes it as cash, that theException
was thrown from there.To better handle exceptions, you should simply use
try...finally
, and let the unhandledException
bubble up.在同一个方法中抛出两次可能是一种特殊情况 - 我无法创建堆栈跟踪,其中同一方法中的不同行彼此跟随。正如该词所说,“堆栈跟踪”向您显示异常遍历的堆栈帧。而且每个方法调用只有一个堆栈帧!
如果您从另一个方法抛出,
throw;
将不会按预期删除Foo()
的条目:如果您修改
Rethrower()
并将throw;
替换为throw ex;
,堆栈跟踪中的Foo()
条目消失。这又是预期的行为。Throwing twice in the same method is probably a special case - I've not been able to create a stack trace where different lines in the same method follow each other. As the word says, a "stack trace" shows you the stack frames that an exception traversed. And there is only one stack frame per method call!
If you throw from another method,
throw;
will not remove the entry forFoo()
, as expected:If you modify
Rethrower()
and replacethrow;
bythrow ex;
, theFoo()
entry in the stack trace disappears. Again, that's the expected behavior.这也算是意料之中的事情了。
修改堆栈跟踪是常见情况,如果您指定
throw ex;
,FxCop 将通知您堆栈已修改。如果您进行throw;
,不会生成警告,但跟踪仍然会被修改。所以不幸的是,现在最好不要抓住前任或将其作为内心的人扔掉。
我认为它应该被视为Windows 影响或类似的-编辑。
Jeff Richter 在他的“CLR via C#” 中更详细地描述了这种情况:
It's something that can be considered as expected.
Modifying stack trace is usual case if you specify
throw ex;
, FxCop will than notify you that stack is modified. In case you makethrow;
, no warning is generated, but still, the trace will be modified.So unfortunately for now it's the best not to catch the ex or throw it as an inner one.
I think it should be considered as a Windows impact or smth like that - edited.
Jeff Richter describes this situation in more detail in his "CLR via C#":
这是 Windows 版本的 CLR 中众所周知的限制。它使用 Windows 内置的异常处理 (SEH) 支持。问题是,它是基于堆栈帧的,并且一种方法只有一个堆栈帧。您可以通过将内部 try/catch 块移动到另一个辅助方法中来轻松解决该问题,从而创建另一个堆栈帧。此限制的另一个后果是 JIT 编译器不会内联任何包含 try 语句的方法。
This is a well known limitation in the Windows version of the CLR. It uses Windows' built-in support for exception handling (SEH). Problem is, it is stack frame based and a method has only one stack frame. You can easily solve the problem by moving the inner try/catch block into another helper method, thus creating another stack frame. Another consequence of this limitation is that the JIT compiler won't inline any method that contains a try statement.