1:错误的出现并不总是编写引用程序的人的原因,有时应用程序会因为应用程序的最终用户引发的动作或运行代码的环境而发生错误。无论如何,我们都应预测应用程序中出现的错误,并相应地进行编码.--在可能出现异常的地方都要try catch。
c# 语言提供了处理这种 情形的最佳工具,称为异常处理机制。
以下介绍:
1:不同场景中捕获和抛出异常的方式。
2:讨论不同名称空间中定义的异常类型及其层次结构。
3:学习如何创建自定义异常类型和捕获异常的不同方式。如:捕获特定泪I型那个的异常或者捕获基类的异常。
4:介绍如何处理嵌套的try块,以及如何以这种方式捕获异常。
5:无论如何都要调用的代码--即使发生异常或者代码带错运行 可以使用try/finally块
6:c#6 新功能:异常过滤器。
一:异常类
在c#中,当出现某个特俗的异常错误条件时,就会创建(或抛出)一个异常对象。
这个对象包含有助于跟踪问题的信息。,也可以创建自己的异常类。
IOException类、CompositionException类和派生于这两个类的类除外不再System名称空间下。
IOException类及其派生类在System.IO名称空间中。System.IO名称空间处理文件数据的读写。
CompositionException及其派生类在System.ComponentModel.Composition名称空间中。
一般情况下,异常没有特定的名称空间,异常类应放在生成异常的类所在的名称空间中,因此与IO相关的异常就在System.IO名称空间中。在许多基类名称空间中都有异常类。
对于.Net 类,一般的异常类System.Exception派生自System.Object,通常不再代码中抛出System.Exception泛型对象,因为他们无法确定错误情况的本质。
在.Net运营库检测到栈已满,它就会抛出StackOverflowException异常--分配给栈的内存区域已满就会抛出这个异常。如果一个方法连续地递归调用它自己,就可能发生栈溢出。递归是要有递归终止条件,不然就可能发生栈溢出。递归有两个条件分别是 递归终止条件和递推公式。
在检测到调用方法时参数不正确,就可以在自己的代码中选择抛出ArgumentException异常或其子类异常。
System.Exception异常的子类包括表示致命错误和非致命错误的异常。
EndOfStreamException--这个异常通常时因为读到文件末尾而抛出的。流表示数据源之间的数据流。
OverflowException--如果要在checked环境下把包含值-40的int类型数据强制转换为uint数据,就会抛出这个异常。
ArgumentNullException类派生于ArgumentException异常类,它专门用于处理所传递的参数值是Null的情况。
二:捕获异常
try:包含组成程序正常操作部分,但这部分程序可能会遇到某些严重错误。
catch:包含的代码处理各种错误情况,这些错误是执行try块中的代码时遇到的。这个块还可以用于记录错误。
finally:finally块包含的代码清理资源或执行通常要在try块或catch块末尾执行其他操作。无论是否抛出异常,都会执行finally块。因为finally块包含了应总是之I型那个的清理代码。如果在finally块中放置return语句。编译器就会标记一个错误。
可以放在finally块中处理的事情有:关闭在try块中打开的连接,如:删除对象或关闭已打开的对象 如数据库连接的关闭,事务的异常回滚之类的处理。finally块是完全可选的。
1: 可以有任意多个catch块,处理不同类型的错误。但不应包含过多的catch块,以防降低应用程序的性能。
2:可以定义过滤器,其中包含的catch块仅在过滤器匹配时,捕获特定块中的异常。
只要在引用程序在try块中遇到一条throw语句,就会立即查找与这个try块对应的catch块。入股有多个与try块对应catch块应用程序就会查找与catch块对应的异常类。
IndexOutOfRangeException异常类,用于处理超出范围的数组。
如果应用程序遇到一条throw语句,就会立即退出栈上的所有方法调用。
编写多个catch块的时候,最上面的catch块应用于最特俗的异常情况,最后是最一般的catch块。
三:System.Exception属性
说明:如果可以进行栈跟踪,则StackTrace的属性值由.Net运行库自动提供。
Source属性总是有.Net运行库填充为抛出异常的程序集的名称。(但可以在代码中修改该属性,提供更具体的信息)。
Data、Message、HelpLink和InnerException属性必须在抛出异常的代码中填充。
方法是在抛出异常浅设置这些属性。
c#的所有异常类的名称通常以Exception结尾。另外,Data属性可以用两种方式设置。
四:异常过滤器
when关键字用来处理异常过滤器
说明:第一个catch块使用when关键字过滤出ErrorCode属性等于405的异常。
when子句的表达式需要返回一个布尔值。如果返回结果是true。这个catch块就处理异常。如果是false,就寻找其他catch块。
五:重新抛出异常
捕获异常时,重新抛出异常也是非常普遍的。再次抛出异常时,可以改变异常的类型。
这样就可以给调用程序提供所发生的更多信息。原始异常可能没有上下文足够信息。还可以记录异常信息。还可以记录异常信息。并给调用程序提供不同的信息。
使用预处理器指令#line,重新编号。
如
#line 8000 public static void ThrowAnException(string message)//8000行 {//8001行 throw new MyCustomException(message);//8002行 }
六:重新抛出异常
如果不应该盖百年异常的类型,就可以使用throw语句重新抛出相同的异常。使用throw但不传递异常对象,会抛出catch块的当前异常,并保存异常信息。
七:使用过滤器添加功能
使用throw语句重新抛出异常时,调用堆栈包含抛出的地址。使用异常过滤器,可以根本不改变调用堆栈。
从图中可以看出 :
通常情况下异常的信息从下往上显示,分别是异常的起源->调用路径异常->抛出异常的地方(也就是说那边 引发了这个错误。)
异常的起源通常是说:导致这个异常的原因是在这个方法内部调用到 而导致了这个错误。
比方说就是:遥控器坏了(异常的起源),抛出异常的地方(遥控器内部的线路短路,这个具体导致遥控器坏的原因),而调用路径异常的就是说(我们操作到触发异常这一路中有经过的路径)
一般情况下,如果编写一个可执行程序,就应铺货仅可能多的异常,并以合理的方式处理它们。
如果编写一个库,最好捕获可以用有效方式处理的异常,或者在上下文中添加额外的信息,抛出其他异常类型。
FileStream和StreamReader都在System.IO名称空间,他们呢都是用于读取文件的基类
FileStream基类主要用于连接文件。StreamReader基类则专门用于读取文本文件。
防御编码技术
在处理错误时,获得错误发生位置的信息常常时有帮助的。可以使用c#编译器直接支持的特性和可选参数。这些特性包括CallerLineNumber、CallerFilePath和CallerMemberName,它们定义在System.Runtime.CompilerServices名称空间中,可以应用到参数上。
这是三个分别是调用行数,调用文件路径 ,代用成员名。
1 class Program 2 { 3 private int _someProperty; 4 public int SomeProperty 5 { 6 get => _someProperty; 7 set 8 { 9 Log(); 10 _someProperty = value; 11 } 12 } 13 static void Main(string[] args) 14 { 15 var p = new Program(); 16 p.Log(); 17 p.SomeProperty = 33; 18 void Action() => p.Log(); 19 Action(); 20 } 21 22 public void Log([CallerLineNumber]int line = -1,[CallerFilePath] string path=null,[CallerMemberName] string name=null) 23 { 24 Console.WriteLine(line<0?"No line":"Line"+line); 25 Console.WriteLine(path ?? "No file path"); 26 Console.WriteLine(name??"No member name"); 27 Console.WriteLine(); 28 } 29 }
在构造函数中使用Log方法时,调用者成员名显示为ctor。在析构函数中,调用者成员名为Finalize,因为它是生成的方法的名称。
CallerMemberName的一个很好的用途是在INotifyPropertyChanged接口的实现中,该接口要求在
方法的实现中传递属性的名称。