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函数,否则将该值赋给变量。