在继续开发应用程序且过程变得更复杂时,你需要对应用程序应用其他调试诊断。
跟踪是在应用程序运行时监视其执行情况的一种方式。 开发.NET 应用程序时,可以向其添加跟踪和调试检测。 可在开发应用程序期间以及部署应用程序后使用该检测。
这一简单技术功能非常强大。 可以在需要多个调试器的情况下使用:
- 传统的调试器可能难以调试长期存在的问题。 通过日志,可以对较长的时间跨度进行详细的事后剖析。 与此相反,调试器限制为只能进行实时分析。
- 多线程应用程序和分布式应用程序通常难以调试。 附加调试器往往会修改行为。 可以根据需要分析详细日志,以了解复杂的系统。
- 分布式应用程序中的问题可能是由许多组件之间的复杂交互导致的。 将调试器连接到系统的每个部分可能并不合理。
- 许多服务不应停止。 附加调试器往往会导致超时失败。
- 问题并非总是可预见的。 日志记录和跟踪旨在降低开销,以便在出现问题的情况下可以始终记录程序。
将信息写入输出窗口
到目前为止,我们一直在使用控制台向应用程序用户显示信息。 其他类型的应用程序是使用 .NET 生成的,其中包含移动应用、Web 应用和桌面应用等用户界面,没有可见的控制台。 在这些应用程序中,System.Console
用于在“后台”记录消息。这些消息可能会显示在 Visual Studio 或 Visual Studio Code 的输出窗口中。 它们还可能会输出到系统日志,如 Android 的 logcat
。 因此,当在非控制台应用程序中使用 System.Console.WriteLine
时,应慎重考虑。
因此除了 System.Console
之外,还可以使用 System.Diagnostics.Debug
和 System.Diagnostics.Trace
。 Debug
和 Trace
都是 System.Diagnostics
的一部分,并且仅在附加了相应的侦听器时写入日志。
选择使用哪种打印样式 API 由用户自己决定。 主要区别包括:
- System.Console
- 始终启用,并始终写入控制台。
- 适用于客户可能需要在发行版中看到的信息。
- 由于这是最简单的方法,所以常常用于临时调试。 此调试代码通常不会签入到源代码管理中。
- System.Diagnostics.Trace
- 仅在定义
TRACE
时启用。 - 写入附加侦听器,默认情况下为 DefaultTraceListener。
- 创建将在大多数生成中启用的日志时,请使用此 API。
- 仅在定义
- System.Diagnostics.Debug
- 仅在定义
DEBUG
时才启用(处于调试模式时)。 - 写入附加调试器。
- 创建仅在调试生成中启用的日志时,请使用此 API。
- 仅在定义
Console.WriteLine("This message is readable by the end user.");
Trace.WriteLine("This is a trace message when tracing the app.");
Debug.WriteLine("This is a debug message just for developers.");
设计跟踪和调试策略时,请考虑自己所需的输出形式。 填充不相关信息的多个 Write 语句将创建难以阅读的日志。 另一方面,如果使用 WriteLine 将相关语句放置在单独的行上,可能会难以区分哪些信息应该在一起。 通常,当需要将来自多个源的信息组合起来创建单个信息性消息时,使用多个 Write 语句。 当需要创建单个完整消息时,使用 WriteLine 语句。
Debug.Write("Debug - ");
Debug.WriteLine("This is a full line.");
Debug.WriteLine("This is another full line.");
此输出来自前面使用 Debug
生成的日志记录:
Debug - This is a full line.
This is another full line.
定义 TRACE 和 DEBUG 常数
默认情况下,当应用程序在调试模式下运行时,将定义 DEBUG
常数。 可以通过在属性组的项目文件中添加 DefineConstants
条目进行控制。 除了对 Debug
配置启用 DEBUG
之外,下面的示例还演示了对 Debug
和 Release
配置启用 TRACE
。
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
如果在未附加到调试器时使用 Trace
,则需要配置跟踪侦听器,如 dotnet-trace。
条件跟踪
除了简单的 Write
和 WriteLine
方法之外,还可以使用 WriteIf
和 WriteLineIf
添加条件。 例如,以下逻辑将检查计数是否为零,然后写入调试消息。
if(count == 0)
{
Debug.WriteLine("The count is 0 and this may cause an exception.");
}
可以在一行代码中重写此逻辑。
Debug.WriteLineIf(count == 0, "The count is 0 and this may cause an exception.");
还可以将这些条件用于 Trace
以及在应用程序中定义的标志。
bool errorFlag = false;
System.Diagnostics.Trace.WriteIf(errorFlag, "Error in AppendData procedure.");
System.Diagnostics.Debug.WriteIf(errorFlag, "Transaction abandoned.");
System.Diagnostics.Trace.Write("Invalid value for data request");
验证是否存在特定条件
断言(或 Assert
语句)测试一个你指定为 Assert
语句的自变量的条件。 如果该条件的计算结果为 true,则不会执行任何操作。 如果条件的计算结果为 false,则断言失败。 如果运行的是调试生成,则程序会进入中断模式。
可以使用位于 System.Diagnostics
命名空间中的 Debug
或 Trace
的 Assert
方法。 程序的发行版中不包含 Debug
类方法,因此它们不增大发行代码的大小,也不会减慢发行代码的速度。
可以自由使用 System.Diagnostics.Debug.Assert 方法测试在代码正确时应为 true 的条件。 例如,假设你编写了一个整数除法函数。 根据数学规则,除数绝不能为零。 你可以使用断言测试此条件:
int IntegerDivide(int dividend, int divisor)
{
Debug.Assert(divisor != 0, $"{nameof(divisor)} is 0 and will cause an exception.");
return dividend / divisor;
}
当在调试器中运行此代码时,将对断言语句进行评估。 但如果是在发行版中,则不会进行此比较,因此也不会产生额外的开销。
备注
使用
System.Diagnostics.Debug.Assert
时,请确保在删除断言后,断言内的任何代码都不会更改程序的结果。 否则,可能会意外引入仅出现在程序的发行版中的 bug。 请特别注意包含函数或过程调用的断言。
正如你所见,利用 System.Diagnostics
命名空间中的 Debug
和 Trace
是在运行和调试应用程序时提供附加上下文的好方法。
练习 - 日志记录和跟踪
现在,已开始开发应用程序,可以向逻辑添加其他诊断了,以便帮助开发人员添加新功能。 我们可以使用我们学习的关于调试诊断的新知识来完成此任务。
写入调试控制台
在调试应用程序之前,让我们添加其他调试诊断。 当应用程序在调试模式下运行时,其他诊断将有助于诊断应用程序。
在 Program.cs
文件的顶部,我们将添加一个新的 using
语句以引入 System.Diagnostics
,以便我们可以使用 Debug
方法。
using System.Diagnostics;
将 WriteLine
语句添加到 Fibonacci
方法的开头,以便在调试代码时清楚地进行展示。
Debug.WriteLine($"Entering {nameof(Fibonacci)} method");
Debug.WriteLine($"We are looking for the {n}th number");
在 for
循环结束时,我们可以打印出每个值。 我们也可以通过使用 WriteIf
或 WriteLineIf
来使用条件打印语句。 仅当 for 循环结束时 sum
为 1 时,才会添加打印行。
for (int i = 2; i <= n; i++)
{
sum = n1 + n2;
n1 = n2;
n2 = sum;
Debug.WriteLineIf(sum == 1, $"sum is 1, n1 is {n1}, n2 is {n2}");
}
调试应用程序,应显示以下输出:
Entering Fibonacci method
We are looking for the 5th number
sum is 1, n1 is 1, n2 is 1
检查带有断言的条件
在某些情况下,当不满足特定条件时,可能需要停止整个正在运行的应用程序。 使用 Debug.Assert
可以检查条件并输出有关应用程序状态的其他信息。 让我们在 return 语句之前添加检查,以确保 n2 为 5。
// If n2 is 5 continue, else break.
Debug.Assert(n2 == 5, "The return value is not 5 and it should be.");
return n == 0 ? n1 : n2;
我们的应用程序逻辑已经是正确的,接下来,让我们将 Fibonacci(5);
更新为 Fibonacci(6);
,其结果会有所不同。
调试应用程序。 当在代码中运行 Debug.Assert
时,调试器将停止应用程序,以便可以检查变量、监视窗口、调用堆栈等。 它还会将消息输出到调试控制台。
---- DEBUG ASSERTION FAILED ----
---- Assert Short Message ----
The return value is not 5 and it should be.
---- Assert Long Message ----
at Program.<<Main>$>g__Fibonacci|0_0(Int32 n) in C:\Users\Jon\Desktop\DotNetDebugging\Program.cs:line 23
at Program.<Main>$(String[] args) in C:\Users\Jon\Desktop\DotNetDebugging\Program.cs:line 3
停止调试,然后通过在终端中输入以下命令,在不调试的情况下运行应用程序。
dotnet run
应用程序在断言失败后终止,并且已将信息记录到应用程序输出。
Process terminated. Assertion failed.
The return value is not 5 and it should be.
at Program.<<Main>$>g__Fibonacci|0_0(Int32 n) in C:\Users\Jon\Desktop\DotNetDebugging\Program.cs:line 23
at Program.<Main>$(String[] args) in C:\Users\Jon\Desktop\DotNetDebugging\Program.cs:line 3
现在,让我们在终端中输入以下命令,以便在 Release
配置中运行应用程序。
dotnet run --configuration Release
应用程序已成功运行到完成,因为我们不再处于 Debug
配置中。
恭喜,你已成功有效地使用 .NET 的功能(包括 Debug.WriteLine
和 Debug.Assert
)调试了代码。 干得不错!