65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 .NET StackTrace 类减少调试时间

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (10投票s)

2011年11月18日

CPOL

5分钟阅读

viewsIcon

52147

downloadIcon

769

使用 .NET StackTrace 类将自己定位到抛出异常的源代码的正确位置。

StackTraceDumperForm in action

摘要

本文介绍了如何使用 .NET 的 StackTrace 类及其堆栈帧在异常处理过程中导航源代码。用于从 StackTrace 中提取信息的类托管在 DLL 程序集中,可用于任何 .NET 3.5 或更高版本的解决方案。

引言

首先,什么是堆栈跟踪?在程序执行过程中,函数或方法调用其他函数和/或方法是很常见的;这些又会调用其他函数和/或方法,从而形成一个调用链。StackTrace 是一个报告此调用序列按照它们发生的正确顺序的结构。

正如 MSDN 所述:“在 Debug 生成配置下,堆栈跟踪信息将最具信息量。默认情况下,Debug 生成包含调试符号,而 Release 生成不包含。调试符号包含构建 StackFrameStackTrace 对象所需的大部分文件、方法名、行号和列信息。”

现在的问题是如何使用这些信息?在调试或短周期开发中,我经常为非常深的类中抛出的异常而苦恼;在这种情况下,我可以做的是:从一个调用导航到下一个调用,深入代码直到达到抛出异常的源头……另一种可能性是读取调用堆栈并查看抛出异常的代码行。StackTrace 类列出了函数/方法调用的序列,因此主要思想是**使用此调用列表来自动化导航源代码的过程**。

使用代码

核心是 .NET 的 StackTrace 类。如果你在代码的任何地方创建一个该类的实例(请参阅下面的代码片段),你就可以检查将你带到该代码点的调用链!

using System.Diagnostics;
. . .
StackTrace stacktrace = new StackTrace();
StackFrame[] frames = stacktrace.GetFrames();
string stackName = string.Empty;
foreach (StackFrame sf in frames){
    System.Reflection.MethodBase method = sf.GetMethod();
    stackName += (method.Name+"|");
}//end_foreach

StackTrace in action

从图中可以看出调用链:Main 方法调用了 _nExecuteAssembly,后者又调用了 RunUsersAssembly,以此类推。

在调试时,这类信息非常有用,因为当你的代码或框架代码抛出异常时,异常会包含大量信息,你可以用这些信息来导航源代码,正式构建某种自动化工具。这就是 StackTraceDumperForm 类所做的。为了让 StackTraceDumperForm 在你的调试会话中提供帮助,你必须通过三件事来配合:

  1. 不要在你的代码中破坏堆栈跟踪(这是推荐的做法)
  2. try-catchcatch 部分,使用异常构建一个 StackTrace 实例,并将其传递给 StackTraceDumperForm 的构造函数
  3. 只运行一个 Visual Studio 实例,这样 StackTraceDumperForm 才能钩住它,找到并高亮显示导致异常的行

让我们一步一步地看。

  1. 不要破坏堆栈跟踪
  2. 这是好的

    try {
        . . .
    }//end_try
    catch( Exception exx ) {
        Debug.WriteLine( exx.Message );
        if( null != exx.InnerException )
             Debug.WriteLine( exx.InnerException.Message );
    
        throw;
    }//end_catch

    因为每当抛出一个异常并被捕获时,都会对其进行检查以显示一些有用的信息,然后**不**破坏调用函数的堆栈就将其重新抛出。

    另一方面,这是坏的

    try {
        . . .
    }//end_try
    catch( Exception exx ) {
        //- NEVER DO THAT:
        //- throws a new exception using a piece of information contained 
        //- inside the original exception and resets the stack trace
        throw new Exception( "The exception message is" + exx.Message );
    }//end_catch

    当然,你显示的是原始异常相同的消息,**但是**每次创建新异常时,调用函数的堆栈都会被重置,并从创建异常的点开始!出于同样的原因,这也是坏的

    try {
        . . .
    }//end_try
    catch( Exception exx ) {
         //- NEVER DO THAT:
         //- throws a new exception using the information contained 
         //- inside the original exception and resets the stack trace
         throw new Exception("The exception is", exx );
    }//end_catch

    实际上,这也是一个糟糕的做法

    try {
        . . .
    }//end_try
    catch( Exception exx ) {
         //- NEVER DO THAT:
         //- throws the original exception but resets the stack trace
         throw exx;
    }//end_catch

    你可以下载演示项目并检查各种情况,也可以使用 StackTraceDumperForm 来可视化堆栈是如何被截断的

    StackTraceDumperForm with stacktrace preserved

    你可以看到堆栈跟踪如何显示有问题的函数(最后一个:DdoItTooEarly 方法),因为堆栈被我们的代码保留了!

    StackTraceDumperForm with stacktrace destroyed

    有问题的函数是 CallAFailureFuncton1(),因为异常是在该函数的 catch 块中创建的。

    StackTraceDumperForm with stacktrace destroyed

    同样,有问题的函数是 CallAFailureFuncton2(),因为异常是在该函数的 catch 块中创建的。

    StackTraceDumperForm with stacktrace destroyed

    最后,有问题的函数是 CallAFailureFuncton3(),因为尽管我们重新抛出了原始异常,但编写 "throw exx;" 而不是 "throw;" 实际上破坏了调用函数的堆栈!

  3. 在 try-catch 的 catch 部分,使用异常构建一个 StackTrace 实例,并将其传递给 StackTraceDumperForm 的构造函数
  4. StackTraceDumperForm 需要的是 System.Diagnostics.StackTrace 的实例,而不是异常。Exception 类的实例包含一个 StackTrace;所以你可以从异常中提取 StackTrace 并将其传递给构造函数!我通常使用的代码片段是

    try {
        . . .
    }//end_try
    catch( Exception exx ) {
        Debug.WriteLine( exx.Message );
        if( null != exx.InnerException )
            Debug.WriteLine( exx.InnerException.Message );
    #if DEBUG
        Dictionary<string, string> debugInfos = new Dictionary<string, string>();
        debugInfos.Add( "Method Name", "MyMethodName" );
        var trace = new System.Diagnostics.StackTrace( ex, true );// true means get line numbers.
        var frm = new StackTraceDumperForm.StackTraceDumperForm( debugInfos, ex.Message, trace );
        frm.ShowDialog();
    #endif
    }//end_catch

    DEBUG 宏只是为了避免在生产代码中,StackTraceDumperForm 会停止代码执行!

  5. 只运行一个 Visual Studio 实例
  6. some automation of StackTraceDumperForm

    你看到 StackTraceDumperForm 中的 Visual Studio 2010 徽标了吗?左侧是字符串:“Go to line...”此信息从 StackTrace 对象中提取,如果你按下按钮(绿色箭头),你将被定位到类中显示的行,但前提是只有一个 Visual Studio 实例,因为 StackTraceDumperForm 无法区分 IDE 的哪个实例包含你感兴趣的类!因此,如果你想让自动化生效,请记住只有一个正在运行的 Visual Studio 实例!还有一件事:绿色信息是你代码的信息,是 StackTraceDumperForm 能够访问的代码;而灰色信息是关于太深而无法访问的代码的信息,即框架的核心类内的代码!最后一件事:另一个小自动化工具由蓝色箭头指示;如果你按下按钮,Visual Studio 的输出窗口将被清除!

结论

我从 2007 年就开始使用这个简单的实用工具,发现它在调试会话中非常有用;所以我决定与大家分享。一如既往,随意使用代码并根据自己的喜好修改它(并告诉我你的想法)!

历史

  • 2010 年 11 月 19 日 - 首次发布。
© . All rights reserved.