Assert 增强






4.66/5 (30投票s)
2003年6月29日
7分钟阅读

99285

723
本文介绍了一个即用型增强型断言对话框及其实现。该对话框提供了“始终忽略”等功能,并显示实际失败的断言表达式。
引言
该框架包含一个用于调试的完整命名空间和一组类。在许多方面,它为调试提供了大量支持,但仍然缺乏基本功能。
本文着重于断言对话框,这可能是调试类提供的最重要的功能。不幸的是,断言对话框非常弱。在本文中,我将讨论如何利用框架提供的一些自定义功能来改进断言对话框。
此外,我还包含了一个增强的、即用型的断言对话框,您可以立即在程序中使用它来改善您的调试体验。
标准断言对话框
我将从讨论默认对话框开始,然后在此讨论之后介绍我的增强型替代品。
默认的断言对话框外观如下。
它显示了来自 Debug.Assert(expr, message)
或 Debug.Fail(message)
控制台的任何文本。不幸的是,如果您在 Debug.Assert(expr)
语句中没有指定消息,调试器将没有任何消息,只报告文件名和行号,因此并不总是清楚是什么触发了它。
默认断言还提供了命名奇怪的“中止”、“重试”和“忽略”按钮。重试调用调试器,中止退出应用程序,忽略从断言点继续执行。
尽管框架提供了许多调试功能,但它未能达到我在 C++(.NET 之前)中的调试体验。一方面,我可能在一个循环中,断言会不断地、无限地触发,而我却无法绕过这个特定的断言。
其次,我必须为我写的每个断言准备一条消息,否则当断言显示时,我将无法看到描述性消息,而是看到行号,这对我来说是没有意义的,直到我进入调试器。由于我写的代码行中大约有很大一部分是断言,这很快就会变得非常麻烦;我还在想这些字符串本身是否会被带入我的发布应用程序,从而增加我的应用程序的大小或使其他人更容易反混淆我的代码。
另一个令人讨厌的问题是对话框的大小不同,如果文件路径太长,我就会有一个宽对话框;如果我深入到一个递归函数中,我就会得到一个高对话框。无论哪种情况,它都会遮挡其下方的区域并导致重绘事件的发出。
SuperAsserter
现在让我们看看我增强的断言对话框。这是我的 SuperAsserter
对话框,带有自定义断言消息“Testing”。
请注意,下方有四个按钮。“Retry”被恰当地重命名为“Debug”,我们还有一个名为“Ignore Always”的新按钮,它可以强制断言对话框在每次调用任何有问题的断言时保持隐藏。
窗口始终保持相同的大小,而与堆栈跟踪的大小无关,但可以根据开发人员的需求调整大小。堆栈跟踪保存在一个滚动文本框中,其内容可以根据需要复制。文本框上方的标签以大粗体文本指示关于断言的最重要信息:问题方法、文件、行号和任何自定义消息。
当 Debug.Assert
不包含自定义消息时,会直接从源文件和行号(如果找到)读取一行;该行的文本被缓存到内存中,以供后续调用相同的断言。该行文本被用作空的自定义消息的替代。然后生成以下对话框。
这个断言对话框有多种改进方式,但目前它已经满足了我的需求。
- 添加一个“将断言保存到文件”的按钮。
- 忽略方法、类、文件或包含断言的堆栈跟踪中的所有断言。
- 报告其他信息,例如 Win32 最后错误消息等。
我还计划制作另一个 SuperCatcher
对话框,当引发异常时可以显示。在这种情况下,对话框将显示所有内部异常,并显示触发每个异常的代码行以及每个异常的消息、类型和属性。
SuperAsserter
可用于控制台和 Windows 应用程序。对于控制台应用程序,必须包含对 System.Windows.Forms
和 System.Drawing
DLL 的引用,因为该对话框是 WinForm
。
要设置 SuperAsserter
,请执行以下任一操作:
- 在
Main
中调用Setup
方法。SuperAsserter.Setup()
- 将
SuperAsserter
单例类实例添加到 Debug.Listeners 或 Trace.Listeners。Debug.Listeners.Remove("Default"); Debug.Listeners.Add(Instance);
Default 是默认侦听器的名称。如果不删除它,您将为每个断言收到两个对话框——新的和旧的。您还可以通过调用 Debug.Listeners.Clear
() 来删除所有侦听器。
调试和跟踪基础
.NET 中调试支持的主要位置是通过 System.Diagnostics
命名空间。
您应该了解的前两个类是 System.Debug
和 System.Trace
,它们几乎相同,不同之处在于 System.Debug
的方法调用在发布版本中被省略,而 System.Trace
的方法调用则两者都存在。
更准确地说,System.Debug
类有一个名为 [ConditionalAttribute("DEBUG")]
的属性。这是编译器支持的另一个用于辅助调试的功能。任何应用了 Conditional
属性的方法,在未定义预处理器符号时都不会被调用。可以通过 C# 编译器开关(例如,在 /d:DEBUG 或 /d:TRACE
中)或项目选项来设置定义。您还可以在文件顶部声明定义——但这必须在任何代码之前编写。
当然,预处理器 #if DEBUG
和 #endif
也可以用来省略调试。对于 C# 和 VB.NET,条件属性是绝对必要的,因为这些语言的预处理器支持有限,不像 C++。否则,在发布阶段就无法消除 Assert()
调用,而不将代码行括在 #if's
中。请注意,CLR 不要求语言遵守条件属性。
System.Debug
和 System.Trace
有几个函数。
Fail(message) |
默认情况下,会生成一个显示文件、行号和堆栈跟踪以及中止、重试和失败选项的对话框。 |
Assert(expression, message) |
如果 expression 为 false,则调用 Fail。 |
Assert(expression) |
如果 expression 为 false,则调用 Fail,message 为空字符串。 |
WriteLine(string) |
默认情况下,会将字符串输出到调试器中的 Output 窗口。内部调用 Win32 API 函数 OutputDebugString 。 |
WriteLineIf(string, expression) |
如果 condition 为 true,则调用 writeline。类似于 if (expression) WriteLine(string) 。 |
以上描述了附加到 Debug
和 Trace
类 Listeners
集合的 DefaultTraceListener
的行为。还可以附加其他类型的侦听器,例如生成事件日志输出的 TraceListener
,以及写入流的侦听器。
还有另外两个提供的 TraceListeners
:TextWriterTraceListener
和 EventLogTraceListener
。要获得写入 stdout 或 stderr 的侦听器,请使用 new TextWriterTraceList(Console.Out or Console.Error)
。
默认 TraceListener
,即 DefaultTraceListener
,可以通过以下方法替换为其他侦听器:
要删除默认 TraceListener
,请调用
Debug.Listeners.Remove("Default").
要删除所有现有侦听器,请调用
Debug.Listeners.Clear();
要添加新侦听器,请调用
Debug.Listeners.Add( new TextWriterTraceListener(Console.Error) );
DefaultTraceListener
在调用 Debug.Assert 或 Debug.Fail
时会调用消息框,而 WriteLine
命令会输出到 Debugger Output 窗口。这包括 Trace
类,调用 Trace.Assert()
命令可能会在已发布的 المنتج 中显示消息框。
自定义 TraceListeners
为了创建 SuperAsserter
,我构建了一个派生自 DefaultTraceListener
的新类。
然后我重写了 void Fail(string message, string detailMessage)
方法。如果传递给它的 expression 为 false,Assert
会自动调用此方法。
Fail
调用一个名为 AssertBox
的 Windows Form 类,该类实现了实际的对话框。
对话框关闭后,控制权将返回到 Fail
。如果用户选择了“Debug”,Fail
会调用 Debugger.Break
。有必要将 DebuggerHiddenAttribute
属性应用于 Fail
方法,否则调试器会中断在 Fail 方法内部,而不是在我们感兴趣的代码中——即最初调用 Assert
的方法。
结论
这是我调试系列文章中的一篇。还将有其他文章。我很感激您的投票;这是我强大的动力。
版本历史
版本 | 描述 |
2003 年 6 月 28 日 | 原始文章。 |