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






4.64/5 (10投票s)
使用 .NET StackTrace 类将自己定位到抛出异常的源代码的正确位置。
摘要
本文介绍了如何使用 .NET 的 StackTrace
类及其堆栈帧在异常处理过程中导航源代码。用于从 StackTrace
中提取信息的类托管在 DLL 程序集中,可用于任何 .NET 3.5 或更高版本的解决方案。
引言
首先,什么是堆栈跟踪?在程序执行过程中,函数或方法调用其他函数和/或方法是很常见的;这些又会调用其他函数和/或方法,从而形成一个调用链。StackTrace
是一个报告此调用序列按照它们发生的正确顺序的结构。
正如 MSDN 所述:“在 Debug 生成配置下,堆栈跟踪信息将最具信息量。默认情况下,Debug 生成包含调试符号,而 Release 生成不包含。调试符号包含构建 StackFrame
和 StackTrace
对象所需的大部分文件、方法名、行号和列信息。”
现在的问题是如何使用这些信息?在调试或短周期开发中,我经常为非常深的类中抛出的异常而苦恼;在这种情况下,我可以做的是:从一个调用导航到下一个调用,深入代码直到达到抛出异常的源头……另一种可能性是读取调用堆栈并查看抛出异常的代码行。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
从图中可以看出调用链:Main
方法调用了 _nExecuteAssembly
,后者又调用了 RunUsersAssembly
,以此类推。
在调试时,这类信息非常有用,因为当你的代码或框架代码抛出异常时,异常会包含大量信息,你可以用这些信息来导航源代码,正式构建某种自动化工具。这就是 StackTraceDumperForm
类所做的。为了让 StackTraceDumperForm
在你的调试会话中提供帮助,你必须通过三件事来配合:
- 不要在你的代码中破坏堆栈跟踪(这是推荐的做法)
- 在
try
-catch
的catch
部分,使用异常构建一个StackTrace
实例,并将其传递给StackTraceDumperForm
的构造函数 - 只运行一个 Visual Studio 实例,这样
StackTraceDumperForm
才能钩住它,找到并高亮显示导致异常的行
让我们一步一步地看。
- 不要破坏堆栈跟踪
- 在 try-catch 的 catch 部分,使用异常构建一个 StackTrace 实例,并将其传递给 StackTraceDumperForm 的构造函数
- 只运行一个 Visual Studio 实例
这是好的
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
来可视化堆栈是如何被截断的
你可以看到堆栈跟踪如何显示有问题的函数(最后一个:DdoItTooEarly
方法),因为堆栈被我们的代码保留了!
有问题的函数是 CallAFailureFuncton1()
,因为异常是在该函数的 catch
块中创建的。
同样,有问题的函数是 CallAFailureFuncton2()
,因为异常是在该函数的 catch
块中创建的。
最后,有问题的函数是 CallAFailureFuncton3()
,因为尽管我们重新抛出了原始异常,但编写 "throw exx;"
而不是 "throw;"
实际上破坏了调用函数的堆栈!
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
会停止代码执行!
你看到 StackTraceDumperForm
中的 Visual Studio 2010 徽标了吗?左侧是字符串:“Go to line...”此信息从 StackTrace
对象中提取,如果你按下按钮(绿色箭头),你将被定位到类中显示的行,但前提是只有一个 Visual Studio 实例,因为 StackTraceDumperForm
无法区分 IDE 的哪个实例包含你感兴趣的类!因此,如果你想让自动化生效,请记住只有一个正在运行的 Visual Studio 实例!还有一件事:绿色信息是你代码的信息,是 StackTraceDumperForm
能够访问的代码;而灰色信息是关于太深而无法访问的代码的信息,即框架的核心类内的代码!最后一件事:另一个小自动化工具由蓝色箭头指示;如果你按下按钮,Visual Studio 的输出窗口将被清除!
结论
我从 2007 年就开始使用这个简单的实用工具,发现它在调试会话中非常有用;所以我决定与大家分享。一如既往,随意使用代码并根据自己的喜好修改它(并告诉我你的想法)!
历史
- 2010 年 11 月 19 日 - 首次发布。