zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C#学习记录——调试和错误处理:Visual Studio中的调试(一):非中断(正常)模式下的调试

2023-09-11 14:14:49 时间

『挑战自我,不进则退。』—— Dzxium-DZX组

活到老,学到老,终身学习,加油!

1、Visual Studio中的调试

在VS中,可以采用两种方式执行应用程序:调试模式或非调试模式。
在VS中执行应用程序时,默认在调试模式下执行。

1.1、非中断(正常)模式下的调试

WriteLine()函数可以把文本输出到控制台。在开发应用程序时,这个函数可以方便地获得操作的额外反馈,例如:

WriteLine("MyFunc()Function about to be called.";
MyFunc("Do something.");
WriteLine("MyFunc()Function execution completed.");

这段代码说明获取MyFunc()函数的额外信息。这么做完全正确,但控制台的输出结果会比较混乱。在开发其他类型的应用程序时,如桌面应用程序,没有用于输出信息的控制台。作为一种替代方法,可将文本输出到另一个位置——IDE中的Output窗口。在调试时这个窗口非常有用。要显示这个窗口,可以选择View|Output。在这个窗口中,可以查看与代码的编译和执行相关信息,包括在编译过程中遇到的错误等,还可以将自定义的诊断信息直接写到这个窗口中,该窗口如下图:
在这里插入图片描述
注意:
使用Output窗口的下拉菜单可以选择几种模式:Build、Deployment和Debug。Build和Debug模式分别显示编译和运行期间的信息。本节提到“写入Output窗口”时,实际上是指“写入Output窗口的Debug模式视图”。

另外,还可以创建一个日志文件,在运行应用程序时,会把信息添加到该日志文件中。

1、输出调试信息

在运行期间把文本写入Output窗口是非常简单的。只要用需要的调用替代WriteLine()调用,就可以把文本写到希望的地方。此时可以使用如下两个命令:

1、Debug.WriteLine()
2、Trace.WriteLine()

这两个命令函数的用法几乎完全相同,但有一个重要区别:第一个命令仅在调试模式下运行,而第二个命令还可用于发布程序。实际上,Debug.WriteLine()命令甚至不能编译到可发布的程序中,在发布版本中,该命令会消失,这肯定有其优点(编译好的代码文件比较小)。

注意: Debug.WriteLine()和Trace.WriteLine()方法包含在System.Diagnostics名称空间内。using static指令只能用于静态类,例如包括WriteLine()方法的System.Console。

练一练:把文本输出到Output窗口:
(1)、将如下代码输入,VS2019

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;

namespace L025_ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] testArray = { 4, 7, 4, 2, 7, 3, 7, 8, 3,9,1,9 };
            int[] maxVaxVallndices;
            int maxVal = Maxima(testArray, out maxVaxVallndices);
            
            WriteLine($"Maximum value{maxVal}found at element indices:");
            foreach(int index in maxVaxVallndices)
            {
                WriteLine(index);
            }
            ReadKey();
        }
        static int Maxima(int[] integers,out int[] indices)
        {
            Debug.WriteLine("Maximum value search started.");
            indices = new int[1];
            int maxVal = integers[0];
            indices[0] = 0;
            int count = 1;
            Debug.WriteLine(string.Format($"Maximum value inttialized to{maxVal},at element index 0."));
            for(int i=1;i<integers.Length;i++)
            {
                Debug.WriteLine(string.Format($"Now looking at element at index {i}."));
                if(integers[i]>maxVal)
                {
                    maxVal = integers[i];
                    count = 1;
                    indices = new int[1];
                    indices[0] = i;
                    Debug.WriteLine(string.Format($"New maximum found.New value is {maxVal},at element index{i}."));
                }
                else
                {
                    if(integers[i]==maxVal)
                    {
                        count++;
                        int[] oldIndices = indices;
                        indices = new int[count];
                        oldIndices.CopyTo(indices, 0);
                        indices[count - 1] = i;
                        Debug.WriteLine(string.Format($"Duplicate maximum found at element index {i}."));
                    }
                }
            }
            Trace.WriteLine(string.Format($"Maximum value {maxVal} found,with {count} occurrences."));
            Debug.WriteLine("Maximum value search completed.");
            return maxVal;
        }
    }
}

(2)、在Debug模式下执行代码,结果如下图:
在这里插入图片描述
(3)、终端应用程序的执行,查看Output窗口中的内容(在Debug模式下),如下所示:

Maximum value search started.
Maximum value inttialized to4,at element index 0.
Now looking at element at index 1.
New maximum found.New value is 7,at element index1.
Now looking at element at index 2.
Now looking at element at index 3.
Now looking at element at index 4.
Duplicate maximum found at element index 4.
Now looking at element at index 5.
Now looking at element at index 6.
Duplicate maximum found at element index 6.
Now looking at element at index 7.
New maximum found.New value is 8,at element index7.
Now looking at element at index 8.
Now looking at element at index 9.
New maximum found.New value is 9,at element index9.
Now looking at element at index 10.
Now looking at element at index 11.
Duplicate maximum found at element index 11.
Maximum value 9 found,with 2 occurrences.
Maximum value search completed.

(4)、使用标准工具栏上的下拉菜单,切换到Release模式,如下所示:
在这里插入图片描述
(5)、再次运行程序,这次在Release模式下运行,并在执行终止时查看Output窗口,结果如下所示:
在这里插入图片描述
样例说明:
本样例应用程序使用一个函数计算整数数组中的最大值。返回一个索引数组,表示最大值在数组中的位置,以便调用代码处理这些元素。

首先在代码开头使用了一个额外的using指令:

using System.Diagnostics;

这简化了对函数的访问,因为它们包含在System.Diagnostics名称空间中,没有using语句,下面的代码:

Debug.WriteLine("Maximum value search completed.");

就需要进一步限定,重新编写这行语句,如下:

System.Diagnostics.Debug.WriteLine("Maximum value search completed.");

Main()中的代码仅初始化一个测试用的整数数组testArray,并声明了另一个整数数组maxVallndices,并存储Maxima()(执行计算的函数)的索引输出结果,接着调用这个函数。函数返回后,代码就会输出结果。

Maxima()稍微复杂一些。
在代码中,各个文本部分都使用Debug.WriteLine()和Trace.WriteLine()函数进行输出,这些函数使用string.Format()函数把变量值嵌套在字符串中,其方式与WriteLine()相同,这比使用+串联运算符更高效。

在Debug模式下运行应用程序时,其最终结果是一条完整记录,它记述了在循环中计算出结果所采取的步骤。在Release模式下,仅能看到计算的最终结果,因为没有调用Debug.WriteLine()函数。

2、跟踪点

跟踪点也可以把信息输出到Output窗口,这是VS的一个功能,而不是C#的功能,但其作用与使用Debug.WriteLine()相同。它实际上是输出调试信息且不修改代码的一种方式。
添加跟踪点的过程如下:
(1)、把光标放在要插入跟踪点的代码行上。跟踪点会在执行这行代码之前被处理。
(2)、右击该行代码,选择Breakpoint|Insert Tracepoint。右击代码行旁边的红圆,选择Setting菜单项。
(3)选中Actions复选框,在Log a message部分的Message文本框键入要输出的字符串。如果要输出变量值,应把变量名放在花括号中。
(4)单击OK按钮。在包含跟踪点的代码行的左边会出现一个红色菱形,该行代码也会突出显示为红色。
看一下添加跟踪点的对话框标题和所需要的菜单选项,显然,跟踪点是断点的一种形式(可以暂停应用程序的执行,就像断点一样)。

在这里插入图片描述
还有一个窗口可用于快速查看应用程序中的跟踪点。要显示这个窗口,可以从VS菜单中选择Debug|Windows|Breakpoints。这是显示断点的通用窗口。可以定制显示的内容,从这个窗口的Columns下拉框中添加When Hit列,显示与跟踪点关系更密切的信息。
在这里插入图片描述
在调试模式下执行这个应用程序,会得到与前面完全相同的结果。在代码窗口中右击跟踪点或者利用Breakpoints窗口中,跟踪点左边的复选框指示是否启用跟踪点;禁用的跟踪点位未被选中,在代码窗口中显示为菱形框,而不是实心菱形。

3、诊断输出与跟踪点

前面介绍了两种输出相同信息的方法,下面分析他们的优缺点。首先,跟踪点与Trace命令并不等价,也就是说,不能使用跟踪点在发布版本中输出信息。这是因为跟踪点并没有包含在应用程序中。跟踪点由VS处理,在应用程序的已编译版本中,跟踪点是不存在的。只有应用程序运行在VS调试器中时,跟踪点才起作用。

跟踪点的主要缺点也是其主要优点,即它们存储在VS中,因此可以在需要时便捷的添加到应用程序中,而且也非常容易删除。如果输出非常复杂的信息字符串,觉得跟踪点非常讨厌,只需要单击表示其位置的红色菱形,就可以删除跟踪点。

跟踪点的一个优点是允许方便地添加额外信息,如$FUNCTION会把当前的函数名添加到输出信息中。这个信息可以用Debug和Trace命令来编写,但比较难。

输出调试信息的两种方法是:
1、诊断输出: 总是要从应用程序中输出调试结果时使用这种方法,尤其是在要输出的字符串比较复杂,涉及几个变量或许多信息的情况下,使用该方法比较好。另外,如果要在执行发布版本的应用程序的过程中进行输出,Trace命令经常是唯一选择。
2、跟踪点: 调试应用程序时,如果希望快速输出重要信息,以便消除语义错误,应使用跟踪点。