Visual Studio调试托管代码
“输出”窗口中的诊断消息
可以使用 Debug 类或 Trace 类(属于 System.Diagnostics 类库)将运行时消息写到“输出”窗口。 如果只在程序的调试版本中输出,则使用 Debug 类。 如果要同时在调试版本和发布版本中输出,则使用 Trace 类。
输出方法
Trace 和 Debug 类提供下列输出方法:
-
在不中断执行情况下输出信息的各种 Write 方法。 这些方法取代了在 Visual Basic 早期版本中使用的 Debug.Print 方法。
-
Debug.Assert 和 Trace.Assert 方法,如果指定的条件失败,这些方法将中断执行并输出信息。 默认情况下,Assert 方法显示对话框中的信息。 有关更多信息,请参见托管代码中的断言。
-
Debug.Fail 和 Trace.Fail 方法,它们总是中断执行并输出信息。 默认情况下,Fail 方法在对话框中显示信息。
除了应用程序的程序输出外,**“输出”**窗口还可以显示下列信息:
-
调试器已经加载或卸载的模块。
-
引发的异常。
-
退出的进程。
-
退出的线程。
托管代码中的断言
断言(或 Assert 语句)测试您作为 Assert 语句的参数指定的条件。 如果此条件计算为 true,不发生操作。 如果此条件计算为 false,则断言失败。 如果正在调试版本中运行,则程序进入中断模式。
在 Visual Basic 和 Visual C# 中,可以使用方法调用,可以从 System.Diagnostics 命名空间下的 Debug 或 Trace 调用 Assert 方法。 在您的程序的发布版本中不包含 Debug 类方法,因此它们不增大或减小发布代码的速度。
C++ 不支持 Debug 类方法。 使用带有条件编译的 Trace 类(如 #ifdef DEBUG...)可获得同样的效果。 #endif.
Debug.Assert 方法
可随意使用 Debug.Assert 方法测试条件,如果代码正确,该条件应保持为真。 例如,假设已编写一个整数除法函数。 根据数学规则,除数永远不能为零。 可以使用断言测试该条件:
[C#]
int IntegerDivide ( int dividend , int divisor )
{ Debug.Assert ( divisor != 0 );
return ( dividend / divisor ); }
当在调试器下运行该代码时,将计算断言语句,但是在发行版本中,将不进行该比较,因此没有额外的系统开销。
以下是另一个示例。 您有一个实现支票活期存款帐户的类,如下所示:
[C#]
float balance = savingsAccount.Balance;
Debug.Assert ( amount <= balance );
savingsAccount.Withdraw ( amount );
在从该帐户中提取钱之前,您想要确保帐户的余额大于准备取出的金额。 可以编写检查余额的断言:
[C#]
float balance = savingsAccount.Balance;
Trace.Assert ( amount <= balance );
savingsAccount.Withdraw ( amount );
注意,当创建代码的发行版本时,对 Debug.Assert 方法的调用将消失。 这意味着在发行版本中检查余额的调用将消失。 若要解决此问题,您应使用不会在发行版本中消失的 Trace.Assert 替换 Debug.Assert:
与对 Debug.Assert 的调用不同,对 Trace.Assert 的调用会增加发行版本的系统开销。
Debug.Assert 的副作用
使用 Debug.Assert 时,确保 Assert 中的任何代码不会更改程序的结果(如果移除 Assert)。 否则,可能会意外地引入一个只在程序的发行版本中出现的 bug。 对于包含函数或过程调用的断言要特别小心,如下面的示例:
[C#]
// unsafe code
Debug.Assert (meas(i) != 0 );
乍一看,使用 Debug.Assert 似乎很安全,但是假设每次调用函数 meas 时该函数都会更新计数器。 当生成发行版本时,由于消除了对 meas 的调用,因此计数器将得不到更新。 这是一个带“副作用”的函数的示例。 消除对具有副作用的函数的调用会导致一个只出现在发行版本中的 bug。 为避免这样的问题,请不要将函数调用放在 Debug.Assert 语句中。 改用临时变量:
[C#]
temp = meas( i );
Debug.Assert ( temp != 0 );
即使在使用 Trace.Assert 时,也需要避免将函数调用放置到 Assert 语句中。 这样的调用应是安全的,因为在发行版本中没有消除 Trace.Assert 语句。 但是,如果习惯上避免这样的结构,则使用 Debug.Assert 时犯错误的可能性很小。
Trace 和 Debug 的要求
如果使用 Visual Studio 向导创建项目,则默认情况下,“发布”配置和“调试”配置中都定义了 TRACE 符号。 默认情况下,只在调试版本中定义 DEBUG 符号。
否则,若要使 Trace 方法工作,程序必须在源文件的顶部放置以下代码之一:
-
Visual Basic 中的 #Const TRACE = True
-
Visual C# 和 C++ 中的 #define TRACE
或者,程序必须是用 TRACE 选项生成的:
-
Visual Basic 中的 /d:TRACE=True
-
Visual C# 和 C++ 中的 /d:TRACE
如果需要在 C# 或 Visual Basic 发行版本中使用 DEBUG 方法,必须在“Release”配置中定义 DEBUG 符号。
C++ 不支持 Debug 类方法。 使用带有条件编译的 Trace 类(如 #ifdef DEBUG...)可获得同样的效果。 #endif. 可以在**“<项目> 属性页”**对话框中定义这些符号。 有关更多信息,请参见为 Visual Basic 调试配置更改项目设置或更改 C 或 C++ 调试配置的项目设置。
Assert 参数
Trace.Assert 和 Debug.Assert 最多有三个参数。 第一个参数是强制的,它是想要检查的条件。 如果仅用一个参数来调用 Trace.Assert(Boolean) 或 Debug.Assert(Boolean),Assert 方法将检查条件,并且如果结果是 false,则向**“输出”**窗口输出调用堆栈的内容。 下面的示例显示 Trace.Assert(Boolean) 和 Debug.Assert(Boolean):
[C#]
Debug.Assert ( stacksize > 0 );
Trace.Assert ( stacksize > 0 );
第二个和第三个参数(如果有)必须是字符串。 如果调用带有两个或三个参数的 Trace.Assert 或 Debug.Assert,则第一个参数是一个条件。 该方法检查此条件,如果结果为 false,则输出第二个和第三个字符串。 下面的示例演示 Debug.Assert(Boolean, String) 和 Trace.Assert(Boolean, String) 使用两个参数的情况:
[C#]
Debug.Assert ( stacksize > 0, "Out of stack space" );
Trace.Assert ( stacksize > 0, "Out of stack space" );
下面的示例显示 Assert 和 Assert:
[C#]
Debug.Assert ( stacksize > 100, "Out of stack space" , "Failed in inctemp" );
Trace.Assert ( stacksize > 0, "Out of stack space", "Failed in inctemp" );
自定义 Assert 行为
如果在用户界面模式中运行应用程序,则当条件失败时,Assert 方法将显示**“断言失败”**对话框。 断言失败时发生的操作是由 Listeners 或 Listeners 属性控制的。
可以通过向 Listeners 集合添加 TraceListener 对象、从 Listeners 集合移除 TraceListener 或者重写现有 TraceListener 的 TraceListener.Fail 方法来自定义输出行为,使其行为不同。
例如,可以重写 TraceListener.Fail 方法来写入事件日志而不是显示**“断言失败”**对话框。
若要以这种方法自定义输出,则程序必须包含侦听器,并且必须从 TraceListener 继承并重写它的 TraceListener.Fail 方法。
在配置文件中设置断言
您可以在程序配置文件和代码中设置断言。
JIT 优化和调试
当调试托管应用程序时,Visual Studio 会默认取消优化实时 (JIT) 代码。 取消 JIT 优化意味着您调试的是非优化代码。 由于代码未优化,因此代码会运行得稍慢一些,但您的调试体验会更全面。 由于调试优化代码要更难一些,因此建议仅在遇到优化代码中发生的 bug 无法在非优化版本中重现时使用。
JIT 优化在 Visual Studio 中由**“在模块加载时取消 JIT 优化”选项控制。 您可以在“选项”对话框中“调试”节点下的“常规”**页上找到此选项。
如果清除**“在模块加载时取消 JIT 优化”选项,您可以调试优化 JIT 代码,但调试的能力会由于优化代码与源代码不匹配而可能受到限制。 因此,调试器窗口(如“局部变量”和“自动”**窗口)显示的信息可能没有调试非优化代码时显示的信息那么多。
另一个重要差异是有关使用“仅我的代码”进行调试。 如果您正在使用“仅我的代码”进行调试,调试器就会将优化代码作为非用户代码处理,不在调试时显示。 因此,在调试 JIT 优化代码时,您可能想关闭“仅我的代码”。
切记,当模块加载时,**“在模块加载时取消 JIT 优化”**选项会取消代码优化。 如果附加到已正在运行的进程,它可能包含已加载的代码、JIT 编译的代码和优化的代码。 **“在模块加载时取消 JIT 优化”选项对这些代码无效,尽管它会影响在附加后加载的模块。 此外,“在模块加载时取消 JIT 优化”**选项不影响用 NGEN 创建的模块,如 WinForms.dll。