显示重新引发的异常的堆栈跟踪,而不是抛出点的堆栈跟踪
我已经在 VS2005 中确认了同样的行为,所以我错误地将其称为 .NET (1.1) bug。
我将原来的问题留在下面,但我修改后的问题是:如何让 Visual Studio 为我提供在“调用堆栈”窗口中捕获并重新抛出的异常的堆栈跟踪,而不是只显示从 throw
语句开始的调用堆栈?
情况是,我在运行时决定全局异常处理程序是打开还是关闭——如果它关闭,我希望 VS 捕获异常,这样我就可以单步执行调用堆栈来找出发生了什么出了问题。
以前,全局异常处理程序要么编译到程序中,要么不编译到程序中。但情况已经改变,现在我们需要在运行时做出决定 - 看起来我可能需要回到宏的方式来做这件事,但没有宏:
if (allow_bubble_up)
{
Foo();
}
else
{
try
{
Foo();
}
catch (Exception e)
{
GlobalExceptionHandler(e);
}
}
但这种方法感觉极其反对干,对我来说。
显然 .NET 1.1 中存在一个错误,如果您有一个空的 throw
语句来重新抛出捕获的异常,则堆栈跟踪将从发生 throw
的位置开始,而不是重新抛出整个异常的堆栈跟踪——至少,我在几个博客上看到它被称为错误,但我无法获得更多有关它的信息。
更具体一点,QuickWatch中$exception
的StackTrace
属性显示了正确的数据,但VS中的Call Stack窗口仅显示了调用堆栈级别抛出语句。
在此示例代码中,我只能看到 Main
的 1 级深度堆栈跟踪,尽管我应该看到对 的几次调用的堆栈跟踪Foo
。
static public void Foo(int i)
{
if (i > 4)
{
throw new ArgumentOutOfRangeException();
}
Foo(i + 1);
}
static void Main(string[] args)
{
bool allow_bubble_up = true;
try
{
Foo(0);
}
catch (Exception e)
{
if (allow_bubble_up)
{
// stack trace just shows Main
throw;
// also just shows Main
//throw new Exception("asdf", e);
// STILL just shows Main
//throw e;
}
else
{
System.Console.WriteLine(e);
}
}
}
Fabrice Marguerie 的博客展示了如何解决 .NET 2.0+ 中某种类型的重新抛出的堆栈跟踪,并在底部表示要查看 Chris Taylor 的博客以了解如何在 .NET 1.1 中执行此操作。我不得不搜索一下 在 archive.org 上找到它。我认为我正确地实现了它,但我仍然在 main 处得到了堆栈跟踪 - 他的解释不是很清楚,而且我不想弄乱代码库(包装现有的集合)另一种方法中的功能)超过必要的范围。
我可以在捕获和重新引发的异常的属性中看到正确的堆栈跟踪,但 VS 显示的可导航堆栈跟踪是无用的,因为它只跟踪 throw
语句。如果我从不捕获并重新抛出异常,我确实会得到完整且正确的堆栈跟踪。
如何在 VS 中显示正确的堆栈跟踪? 我希望有某种简单的解决方法,而且我只是搜索了错误的术语。
不幸的是,它必须是 VS2003+C#。
如果不清楚,这里有一个屏幕截图(您可能需要右键单击并查看图像):
I've confirmed this same behavior in VS2005, so I was wrong to call it a .NET (1.1) bug.
I'm leaving the original question below, but my revised question is this: how do I get Visual Studio to give me the stack trace of the exception I've caught and re-thrown in the Call Stack window, rather than only displaying the call stack from the point of the throw
statement?
The situation is that I am deciding at runtime whether the global exception handler is on or off -- if it's off, I want VS to catch the exception so I can step through the call stack to figure out what went wrong.
Previously, the global exception handler was either compiled into the program or not. But the situation has changed, and now we need to decide at runtime -- it's looking like I might need to go back to the macro way of doing it, but without macros:
if (allow_bubble_up)
{
Foo();
}
else
{
try
{
Foo();
}
catch (Exception e)
{
GlobalExceptionHandler(e);
}
}
But that approach feels extremely against DRY, to me.
Apparently there is a bug in .NET 1.1 where if you have an empty throw
statement to re-throw a caught exception, the stack trace is started from where that throw
occurred, instead of the stack trace of the whole exception being re-thrown -- at least, I've seen it called a bug on a couple blogs, but I haven't been able to get much more information on it.
To be a little more specific, the StackTrace
property of $exception
in QuickWatch shows the correct data, but the Call Stack window in VS only shows the call stack to the level of the throw statement.
In this sample code, I can only see a 1-level-deep stack trace of Main
, even though I should see a stack trace of a couple calls to Foo
.
static public void Foo(int i)
{
if (i > 4)
{
throw new ArgumentOutOfRangeException();
}
Foo(i + 1);
}
static void Main(string[] args)
{
bool allow_bubble_up = true;
try
{
Foo(0);
}
catch (Exception e)
{
if (allow_bubble_up)
{
// stack trace just shows Main
throw;
// also just shows Main
//throw new Exception("asdf", e);
// STILL just shows Main
//throw e;
}
else
{
System.Console.WriteLine(e);
}
}
}
Fabrice Marguerie's blog shows how to work around re-thrown stack traces of some sort for .NET 2.0+, and at the bottom he says to check Chris Taylor's blog for how to do it in .NET 1.1. I had to search a bit to find it on archive.org. I think I implemented it correctly, but I still got a stack trace just at main -- his explanation wasn't terribly clear, and I'd prefer not to mess with the code base (wrap existing set of functionality in another method) any more than necessary.
I can see the correct stack trace in the properties of the caught and re-raised exception, but the navigable stack trace that VS shows is useless since it only tracks from the throw
statement. If I never catch and re-throw the exception, I do get a full and proper stack trace.
How do I get the right stack trace displayed in VS?
I'm hoping there's some sort of simple workaround, and that I've just been searching the wrong terms.
And unfortunately, it has to be VS2003+C# for this.
If it wasn't otherwise clear, here's a screenshot (you'll probably need to right-click and view image):
alt text http://img257.imageshack.us/img257/1124/40727627.png
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
Visual Studio 将显示其停止位置的调用堆栈。
如果发生未处理异常,它将在抛出该异常的地方停止。即你的“抛出”声明。但是,如果您的代码处理异常,则 Visual Studio 会假定您知道自己在做什么,并忽略该异常。它只会在 Main() 中重新抛出异常时捕获异常,因为您没有在程序中处理它。
如果您想在 Visual Studio 中捕获原始异常,您有两个选择:
不要捕获代码中的异常。默认情况下,Visual Studio 仅在未处理异常时停止。这当然意味着您的程序不会在运行时处理异常,因此这不是一个非常有用的方法!
使用您的代码捕获并重新引发异常(正如您所做的那样),但将 Visual Studio 配置为在首次引发内部异常时停止。转到“调试”>“异常”并勾选“公共语言运行时”异常框(以停止任何异常)或浏览子树以启用特定异常的异常捕获(提示:如果您知道异常名称,请点击“查找...”按钮并输入名称的一部分,例如“FileNotFound”,以快速查找异常)。这将使 VS 在内部异常处停止,并且如果您在检查异常详细信息后选择继续执行,则仅继续执行 catch{} 语句。
Visual Studio will show the call stack of the place where it stops.
In the case of an unhandled exception, it will stop where that exception is thrown. i.e. your "throw" statement. But if your code handles the exception, then Visual Studio assumes you know what you're doing, and ignores the exception. It will only catch the exception when it is re-thrown in Main() because you aren't handling it in your program.
If you want to catch the original exception in visual studio, you have two options:
don't catch the exception in your code. Visual Studio by default will only stop at unhandled exceptions. This of course means your program won't handle exceptions at runtime, so isn't a very useful approach!
use your code to catch and re-throw the exception (as you are doing), but configure Visual Studio to stop when the inner exception is first thrown. Go to Debug>Exceptions and tick the "common language runtime" exceptions box (to stop for any exception) or browse the subtree to enable exception catching for specific exceptions (hint: if you know the exception name, hit the Find... button and enter part of the name, e.g. "FileNotFound", to quickly find the exception). This will make VS stop at the inner exception, and only move on to your catch{} statement if you choose to continue execution after examining the exception details.
您可以抛出一个新的异常,将异常 e 作为内部异常。然后读取内部异常的堆栈跟踪。
You could throw a new exception, placing the exception e as an inner exception. Then read the stacktrace of the inner exception.
事实证明,如果您知道正确的搜索术语,我试图解决的问题就有答案了。在 MSIL 中,它称为异常过滤,以及它在 VS2003 中可用。
在 Visual Basic.NET 中,有一个名为“catch-when”的构造,它仅在给定谓词通过时才执行 catch。 此 MSDN 博客< /a> 有一个很好的例子,展示了 catch-when 在 VB.NET 中的工作原理与 C# 的 catch-throw 的结果(如我的)的比较。
最后,MSDN 有一个名为 Exception Filter Inject 的工具,可用于“提供异常过滤器支持”对于不支持异常过滤器的语言(例如 C#)” - 问题是它在现有程序集上运行,因此如果您最终使用它,它确实会在构建过程中引入一个尴尬的阶段。
在我发现 Exception Filter Inject 之前,我最终实现了一个简短的函数,它接受一个“功能”委托和一个“catch”委托,并且如果允许异常冒泡,则只调用该函数,否则在 try- 中调用该函数catch,在捕获的异常上调用 catch 委托。
我想要做的,以某种方式引导我找到异常过滤,是能够设置要在运行时捕获的异常类型 - 如果异常应该冒泡,我会尝试捕获永远不会被调用的子类异常,否则我只会捕获基本异常。我真的不确定这在 .NET 1.1 中是否可能,因为这基本上需要一个泛型——但对于 Reflection 来说这可能是可能的,我只是在我的研究中从未走得那么远。
It turns out that if you know the right terms to search, there is an answer to the problem I was trying to solve. In MSIL, it's called exception filtering, and it is available in VS2003.
In Visual Basic.NET, there is a construct called "catch-when" that will only execute a catch when a given predicate passes. This MSDN blog has a great example of how catch-when works in VB.NET vs. the results (like mine) of C#'s catch-throw.
Finally, MSDN has a tool called Exception Filter Inject that can be used to "provide exception filter support for languages (such as C#) which do not have exception filter support" - the catch is that it runs on an existing assembly, so it does introduce an awkward stage in the build process if you end up using it.
Before I found Exception Filter Inject, I ended up implementing a short function that took a "functional" delegate and a "catch" delegate, and would just call the functional if exceptions were allowed to bubble up, otherwise called the functional in a try-catch, calling the catch delegate on a caught exception.
What I wanted to do, that somehow lead me to find exception filtering, was to be able to set the type of the exception to catch at runtime - if exceptions were supposed to bubble up, I would have tried catching a subclassed Exception that would never be invoked, otherwise I would have just caught a basic Exception. I'm really not sure if that would have been possible in .NET 1.1 or not, since that basically would require a generic -- but it may have been possible with Reflection, I just never got that far in my research.
如果我理解您的消息,则正确的错误的堆栈跟踪与特定时间点的当前调用堆栈之间存在混淆(可能不是您,而是其他人阅读您的消息)(这不是您想要的) )
但是,一旦进入异常处理例程,Foo 例程就已完成,因此我看不到它如何成为当前调用堆栈的一部分。
除了启用“在第一个异常时中断”之外,我看不出这是如何工作的,并且不知道 VS2003 或 VS2005 中的任何内容将对此有所帮助。 (也许是VS2010中新的调试/重播功能)
If I understand your message, there is confusion (maybe not by you, but by others reading your message) between the stack trace of the error which is correct and the current call stack at a particular point in time (which is not what you want)
However, once you get into your exception handling routine, the Foo routine has finished and so I can't see how it could be part of your current call stack.
Apart from enabling 'break at first exception' , I can't see how this would work and am not aware of anything in VS2003 or VS2005 which will help this. (Maybe the new debugging/replay features in VS2010)
您所描述的是“调用堆栈”窗口的预期行为。当 Visual Studio 由于 UnhandledException 在
throw
行处中断时,从throw
行开始显示调用堆栈是正确的。归结为 Visual Studio 的调用堆栈窗口不知道异常中包含的堆栈跟踪。
What you're describing is the expected behavior of the Call Stack window. When Visual Studio breaks at the
throw
line due to an UnhandledException, it is correct to have the call stack show starting at thethrow
line.It boils down to Visual Studio's Call Stack Window isn't aware of the stack trace contained in your exception.