C# 调试和错误处理 《C#入门经典》

1. Visual Studio中的调试

可以采用两种方式执行应用程序调试模式(F5)非调试模式(Ctrl+F5)

在VS中执行应用程序时,默认是调试模式

VS允许在两种配置下生成应用程序调试(默认)发布

调试:调试程序包含应用程序的符号信息,符号信息意味着跟踪(例如)未编译代码中使用的变量名,这样它们就可以匹配已编译的机器码应用程序中现有的值。此类信息包含在.pdb文件中,这些文件位于计算机的Debug目录下。

发布:发布配置会优化应用程序代码,完成应用程序的开发后,一般应给用户提供发布版本,因为发布版本不需要调试版本所包含的符号信息。

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

ouput窗口(输出窗口)

使用调试可以找出并修改未按预期方式执行的那些代码
一般情况下,可以先中断程序的执行,再进行调试,或者注上标记,以便以后加以分析

在VS术语中,应用程序可以处于运行状态,也可以处于中断模式,即暂停正常的执行。

1.1.1 输出调试信息(诊断输出)

在运行期间把文本写入Output窗口:用所需的调用(调试和发布)代替Console.WriteLine()调用,就可以把文本写道所希望的位置:
Debug.WriteLine();
Trace.WriteLine();

这两个方法包含在System.Diagnostics名称空间内,在代码顶部有using System.Diagnostics
Debug.WriteLine()命令甚至不能编译到可发布的程序中,在发布版本中,该命令会消失,编译好的代码文件会比较小,肯定有优点。

这两个函数的用法与Console.WriteLine()不同。其唯一的字符串参数用于输出消息<message>,而不需要使用{X}语法插入变量值,即必须使用+来在字符串中插入变量值,除非用string.Format()函数。

两个函数还可以有第二个字符串参数(可选),用于显示输出文本的类别<category>

这些函数的一般输出如下所示:
<category>: <message>
例如:Debug.WriteLine ("Added 1 to i", "MyFunc");
其输出结果为:MyFunc: Added 1 to i.

using System;
using System.Diagnostics;//使用Debug.函数必须在开头写的代码

namespace Ch07Ex01
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] testArray = { 4, 7, 4, 2, 7, 3, 7, 8, 3, 9, 1, 9 };
            int maxVal = Maxima(testArray, out int[] maxValIndices);/*testArray是Main()输入进函数的
                                                                     *因为有out,maxValIndicess是新声明用来存储Maxima()的引索输出结果的名称不一定要和indices一样
                                                                     *maxVal是函数中返回的值,所以名称和函数里的一样*/
            Console.WriteLine($"Maximum value {maxVal} found at element indices:");
            foreach (int index in maxValIndices)
            {
                Console.WriteLine(index);
            }
            Console.ReadKey();
        }
        static int Maxima(int[] integers, out int[] indices)/*indices并不是一个足以存储源数组中每个索引的数组,而是返回一个刚好能容纳搜索到的索引的数组
                                                             * 必须通过在搜索过程中连续重建不同长度的数组来实现(因为最大值可能不值一个,所以索引数目不一定只是1,遇到了相同的时便要重建数组)
                                                             * out也可以输出数组类型的参数*/
        {
            Debug.WriteLine("Maximum value search started.");   //在调试中 显示代码允许到哪个步骤了
            indices = new int[1];       //声明索引为一个大小为1的int数组(因为索引只包含一个数字)
            int maxVal = integers[0];   //声明最大值最开始是数组的第一个数(从第一个数开始检查)
            indices[0] = 0;             //声明索引数组元素最开始是0
            int count = 1;              //occurences发生次数
            Debug.WriteLine(string.Format(              //Debug.WriteLine()函数利用string.Format()函数把变量值嵌套在字符串中
               $"Maximum value initialized to {maxVal}, at element index 0.")); //在调试中表明最大值初始化为数组第一个元素
            for (int i = 1; i < integers.Length; i++)   //循环数组中每一个数,此时i不用从0开始,因为最大值初始化的就是0代表的元素
            {
                Debug.WriteLine(string.Format(
                   $"Now looking at element at index {i}."));   //在调试中表明现在进行到数组中第几个元素了
                if (integers[i] > maxVal)
                {
                    maxVal = integers[i];       //若检查元素比当前的maxVal大则替代
                    count = 1;              //要覆盖count,因为此时出现的是最新的最大值!!
                    indices = new int[1];   //要新声明一个大小为1的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;     //oldIndices是用来准备提供重建数组的内容
                        indices = new int[count];        //count是用来准备提供重建数组的新大小
                        oldIndices.CopyTo(indices, 0);      //将oldIndices中的值复制到新的indices数组中
                        indices[count - 1] = i;     //将最大值代表的索引放在最新的位置上(若最大值有2个,则放在第二个位置上)
                        Debug.WriteLine(string.Format(
                           $"Duplicate maximum found at element index {i}."));
                    }
                }
            }
            Trace.WriteLine(string.Format(//Trace发布放在最后
               $"Maximum value {maxVal} found, with {count} occurrences."));
            Debug.WriteLine("Maximum value search completed.");
            return maxVal;      //返回了一个值maxVal
        }
    }
}

oldIndices.CopyTo(indices, 0);
 将oldIndices中的值复制到新的indices数组中
这个函数的参数是一个目标数组和一个用于复制第一个元素的索引,并将所有的值都粘贴到目标数组中

string.Format()
Debug.WriteLine()函数利用string.Format()函数把变量值嵌套在字符串中,其方式与Console.WriteLine()相同了,可以用$了

1.1.2 跟踪点(tracepoint)

断点的一种

作用和Debug.WriteLine()相同,实际上是输出调试信息且不修改代码的一种方式

1.1.3 诊断输出与跟踪点

与Trace命令不同,跟踪点并没有包含在应用程序中,跟踪点由VS处理,在应用程序的已编译版本中,跟踪点是不存在的,只有应用程序在VS调试器中运行时,跟踪点才起作用

1.2 中断模式下的调试

1.1.1 进入中断模式

运行程序时单击IDE中的Pasuse按钮,但这样不能很好地控制停止程序运行地位置

(1)断点
断点是源代码中自动进入中断模式的标记:
1.遇到断点时,立即进入中断模式
2.遇到断点时,如果布尔表达式的值为true,就进入中断模式(右键断点选择条件)
3.遇到某断点一定的次数后,进入中断模式
4.在遇到断点时,如果自从上次遇到断点以来变量的值发生了变化,就进入中断模式
注意:上述功能仅用于调试程序,若编译发布程序,将忽略所有断点

(2)抛出一个未处理的异常时选择进入该模式

(3)生成一条判定语句(assertion)时中断
判断语句是可以用用户定义的信息中断应用程序的指令。
通常用于应用程序的开发过程,作为测试程序能否平滑运行的一种方式。

当遇到判定语句时
Abort:终止应用程序的执行;
Retry:进入中断模式;
Ignore:让应用程序像往常一样继续执行

Debug.Assert(maxVal < 10, "maxVal is 10 or greater.", "Assertion occurred in Main()");

判定函数:
Debug.Assert(bool, "text 对话框", "text 输出窗口")
Trace.Assert(bool, "text 对话框", "text 输出窗口")

其中当bool是false时才会触发判定语句
第一个字符串提供有关错误的简短描述
第二个字符串提供下一步该如何操作的指示

Debug.Assert(maxVal < 10, "Variable out of bounds.", "Pleas contact vendor with the error code KCW001.");

2. 错误处理

编写足够健壮的代码预料到错误的发生,而不必中断程序的执行

2.1 try...catch...finally

C#语言包含结构化异常处理(structured Exception Handling, SEH)的语法。

用三个关键字可以标记出能处理异常的代码和指令:try、catch和finally
它们有一个关联的代码块,必须在连续的代码行中使用

            try
            {
                ...
            }
            catch(<exceptionType> e) when (<filterIsTrue>)
            {
                await <methodName(e);>
                ...
            }
            finally
            {
                await < methodName;>
                ...
            }

await关键字用于支持先进的异步编程技术,避免瓶颈,且可以提高应用程序的总体性能和响应能力
也可以只有try块和finally块,而没有catch块
若有一个或多个catch块,finally块就是可选的,否则就是必需的 

try
包括抛出异常的代码
(“抛出”表示“生成”或“导致”)

catch
包括抛出异常时要执行的代码

catch块可使用<exceptionType>,设置为只响应特定的异常类型,以便提供多个catch块。
还可以完全省略这个参数,让通用的catch块响应所有异常
异常过滤:通过在异常类型表达式后添加when关键字来实现,若发生了该异常类型,且过滤表达式是true,就执行catch块中的代码

finally
包含式中会执行的代码

如果没有产生异常,则在try块之后执行,如果处理了异常,则在catch块之后执行,或者在未处理的异常“上移到调用堆栈”之前执行
上移到调用堆栈:SEH允许嵌套try...catch...finally块,直接嵌套或在try块中的函数调用嵌套
即finally块要在嵌套之前

try块的代码中出现异常后:
1)try块在发生异常的地方中断程序的执行
2)如果有catch块,就检查该块是否匹配已抛出的异常类型。如果没有catch块,就执行finally块(如果没有catch块,就一定有finally块)
3)如果有catch块,但它与已发生的异常类型不匹配,就检查是否有其他catch块
4)如果有catch块匹配已发生的异常类型,且有一个异常过滤器是true,就执行它包含的代码,再执行finally块(如果有的话)
5)如果有catch块匹配已发生的异常类型,但没有异常过滤器,就执行它包含的代码,再执行finally块(如果有的话)
6)如果catch块都不匹配已发生的异常类型,就执行finally块(如果有的话)
7)如果有两个处理相同异常类型的catch块,只执行一个(异常过滤器为true的)

using System;
namespace Ch07Ex01                                                                      //如何用try...cash...finally标记出能处理异常的代码和指令
{
    class Program
    {
        static string[] eTypes = { "none", "simple", "index",                           //声明了一个全局字符串数组变量,里面包含了异常的各种情况
                                   "nested index", "filter"};
        static void Main(string[] args)
        {
            foreach (string eType in eTypes)                                            //访问eTypes里的每一个元素
            {
                try                                                                     //在发生异常的地方中断程序的执行
                {
                    Console.WriteLine("Main() try block reached.");
                    Console.WriteLine($"ThrowException(\"{eType}\") called.");          
                    ThrowException(eType);                                             //用每一个元素调用函数
                    Console.WriteLine("Main() try block continues.");
                }
                catch (System.IndexOutOfRangeException e) when (eType == "filter")     //eTypes里eTypes里匹配该类型且等于filter的异常
                {
                    Console.BackgroundColor = ConsoleColor.Red;
                    Console.WriteLine("Main() FILTERED System.IndexOutOfRangeExcepetion" +
                                      $"catch block reached. Message:\n\"{e.Message}\"");//e.Message可以获取描述当前异常的消息
                    Console.ResetColor();                                               //将前台和后台控制台颜色设置为默认值
                }
                catch (System.IndexOutOfRangeException e)                              //eTypes里匹配该类型的异常
                {
                    Console.WriteLine("Main()System.IndexOutOfRangeExcepetion" +
                    $"catch block reached. Message:\n\"{e.Message}\"");
                }
                catch                                                                  //剩下的“simple”
                {
                    Console.WriteLine("Main() general catch block continues.");
                }
                finally
                {
                    Console.WriteLine("Main() finally block continues.");
                }
                Console.WriteLine();
            }
            Console.ReadKey();
        }
        static void ThrowException(string exceptionType)                                //形参是可以匹配的异常类型
        {
            Console.WriteLine($"ThrowException (\"{exceptionType}\") reached");          //现在处理异常类型exceptionType
            switch (exceptionType)                                                      //利用switch来分开数组中的情况
            {
                case "none":                                                            //当exceptionType中有元素是none
                    Console.WriteLine("Not throwing an exception");                     //不抛出异常,即轮到none时无事发生
                    break;
                case "simple":                                                          //当exceptionType中有元素是simple
                    Console.WriteLine("Throwing System.Exception");                     //抛出一个一般异常
                    throw new System.Exception();                                       
                case "index":                                                           //当exceptionType中有元素是index
                    Console.WriteLine("Throwing System.IndexOutOfRangeExcepetion.");    //生成一个System.IndexOutOfRangeExcepetion异常
                    eTypes[5] = "error";                                                //这种表示是生成异常的另一种方式,索引是6,会生成一个System.IndexOutOfRangeExcepetion异常
                    break;
                case "nested index":                                                    //当exceptionType中有元素是nested index
                    try
                    {
                        Console.WriteLine("ThrowException(\"nested index\")" +          //到达ThrowException函数的try块
                                          "try block reached.");
                        Console.WriteLine("ThrowException(\"index\") called.");         /*使用index参数嵌套调用ThrowException函数*/
                        ThrowException("index");                                        /*这样会返回到L45再到case "index"中,生成一个System.IndexOutOfRangeExcepetion异常*
                                                                                         * 可以使用Step Over跳过其中的代码行,因为前面case "index"已经单步执行过了 *
                                                                                         * 由于没有明确匹配这种异常的catch块,所以执行general catch块*/
                    }
                    catch
                    {
                        Console.WriteLine("ThrowException(\"nested index\") general" + " catch block reached.");
                        throw;                                                          //抛出一个异常
                    }
                    finally
                    {
                        Console.WriteLine("ThrowException(\"nested index\")" + " finally catch block reached.");
                    }
                    break;
                case "filter":
                    try
                    {
                        Console.WriteLine("ThrowException(\"filter\")" +
                                          "try block reached.");
                        Console.WriteLine("ThrowException(\"index\") called.");
                        ThrowException("index");
                    }
                    catch
                    {
                        Console.WriteLine("ThrowException(\"filter\") general" + "catch block reached.");
                        throw;
                    }
                    break;
            }

        }
    }
}

throw关键字可以抛出一个异常,需要为这个关键字提供新初始化的异常作为其参数
throw new System.Exception()这里使用Syste名称空间中的另一个异常System.Exception
在case中使用throw时,就不用break了
单以throw一个字结尾,那么抛出的便是已经确定了的异常,不用再new了

e.Message 可以输出存储在异常中的信息

 Console.BackgroundColor = ConsoleColor.Red;
 Console.ResetColor(); 
将前台和后台控制台颜色设置为红色/默认值

System.IndexOutOfRangeException:一种异常类型

2.2 throw表达式

throw不仅能用对已经发生的操作进行编码的代码语句中,再表达式中也可以用:
friend ?? throw new ArgumentNullException (paraName: nameof(friend), message: "null")

双问号(??):空值合并操作符(null-coalescing operator),检查所赋的值是否为null,若为null,则抛出ArgumentNullException函数,否则将该值赋给变量。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值