关于.net:C#重新抛出异常:如何在IDE中获取异常堆栈?

C# rethrow an exception: how to get the exception stack in the IDE?

以前有过关于重新引发异常的正确方法的讨论。相反,这个问题是关于如何在使用rethrow时从Visual Studio获得有用的行为。

考虑此代码:

1
2
3
4
5
6
7
8
9
10
11
12
   static void foo() {
        throw new Exception("boo!");
    }

    static void Main(string[] args) {
        try {
            foo();
        } catch (Exception x) {
            // do some stuff
            throw;
        }
    }

出现的异常具有正确的堆栈跟踪,将foo()显示为异常的源。然而,GUI调用堆栈窗口只显示main,而我期望它显示异常的调用堆栈,一直到foo。

当没有回传时,我可以使用GUI快速浏览调用堆栈,查看是什么调用导致了异常以及我们是如何到达异常的。

有了回程,我想做同样的事情。相反,GUI显示的调用堆栈对我没有用处。我必须将异常详细信息复制到剪贴板上,粘贴到记事本上,然后手动导航到我感兴趣的调用堆栈的任何函数。

顺便说一下,如果我加上[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)],或者如果我把catch改为catch (Exception),我会得到同样的行为。

我的问题是:考虑到我使用的代码是rethrow,有人能建议一种方便的方法来导航与异常关联的调用堆栈吗?我正在使用Visual Studio 2010。


调试器在Main中的throw处中断,因为该异常未经处理。默认情况下,调试器只在未处理的异常上中断。在Main处停止后,异常中存在来自foo的原始异常的调用堆栈,但所有其他上下文都已丢失(例如,局部变量、堆栈/内存状态)。

听起来您希望调试器在foo中的throw上中断,因此您应该告诉调试器在第一次出现异常时中断:

  • 调试?例外…(ctrl+alt+e)
  • 检查"抛出"以了解您关心的异常类型(在本例中,是commange语言运行时异常)
  • 单击确定
  • 开始调试
  • 在这种情况下,当foo抛出异常时,调试器将立即中断。现在,您可以在原始异常的上下文中检查堆栈、局部变量等。如果继续执行(f5),调试器将在Main中的rethrow上再次中断。

    采用另一种方法,如果运行的是VS2010 Ultimate,还可以使用IntelliTrace"向后调试",以在异常发生时查看参数、线程和变量。有关详细信息,请参阅此msdn文章。(完全公开:我在一个与IntelliTrace密切相关的团队中工作)。


    如果使用resharper,则可以将异常堆栈跟踪复制到剪贴板,然后在菜单中选择:resharper>工具>浏览堆栈跟踪(ctrl+e,t)。它将显示具有可点击位置的stacktrace,因此您可以快速导航。

    http://www.jetbrains.com/resharper/webhelp/images/reference_uuwindows_uu stack_trace_explorer.png

    当从用户那里挖掘日志时(如果记录了异常的stacktrace),此功能也非常有用。


    不是说你应该重新抛出,但这里有一篇关于如何保留堆栈跟踪的博客文章,基本上可以归结为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    private static void PreserveStackTrace(Exception exception)
    {
      MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
        BindingFlags.Instance | BindingFlags.NonPublic);
      preserveStackTrace.Invoke(exception, null);
    }

    ...
    catch (Exception ex)
    {
      // do something
      // ...
      PreserveStackTrace(ex);
      throw;
    }


    Mike Stall为您的问题提供了一个非常简单的解决方案:

    用属性[DebuggerNonUserCode]标记重新引发异常的方法。

    IDE会认为这不是您的代码,不会在这样的位置中断调试器,相反,它会在堆栈中进一步查找,显示下一次重新执行或初始异常位置。

    (如果下一次重流也很烦人,也可以标记为[DebuggerNonUserCode],等等…)