异常简介
C sharp中的异常用于处理系统级和应用程序级的错误状态,它是一种结构化、统一的类型安全的处理机制。异常处理相对于返回错误代码的一个最大优点在于,异常可以被自动传递,这样,在编程时异常更加难以被忽视。
C#的异常机制非常类似于C++的异常处理机制,但是还是有一些重要的区别:
1,在C#中,所有的异常必须由从System.Exception派生的类来表示。在 C++ 中,可以使用任何类型的任何值表示异常。
2,在C#中,利用finally 块可编写在正常执行和异常情况下都将执行的终止代码。在C++中,很难在不重复代码的情况下编写这样的代码。
3,C# 中,系统级的异常如溢出、被零除和 null 等都对应地定义了与其匹配的异常类,并且与应用程序级的错误状态处于同等地位。
引发异常的原因
1.throw 语句用于立即无条件地引发异常。控制永远不会到达紧跟在 throw 后面的语句。
2.在执行C#语句和表达式的过程中,有时会出现一些例外情况,使某些操作无法正常完成,此时就会引发一个异常。例如,在整数除法运算中,若分母为零引发 System.DivideByZeroException。
System.Exception 类
System.Exception类是所有异常的基类型。若干个异常类直接从Exception继承。
ApplicationException和SystemException继承该类,几乎是所有运行时异常的基础。
此类具有一些所有异常共享的值得注意的属性:
· Message 是string类型的一个只读属性,它包含关于所发生异常的原因的描述(易于人工阅读)。
· InnerException 是 Exception 类型的一个只读属性。
如果它的值不是 null,则它所引用的是指导致了当前异常的那个异常,即表示当前异常是在处理那个InnerException的catch 块中被引发的。
如果它的值为 null,则表示该异常不是由另一个异常引发的。
以这种方式链接在一起的异常对象的数目可以是任意的。此属性可用来在异常处理过程中创建和保留一系列异常。可使用此属性创建一个新异常来包含以前捕捉的异常。原始异常可
由 InnerException 属性中的第二个异常捕获,这使处理第二个异常的代码可以检查附加信息。
例如,假设有一个读取文件并格式化相应数据的方法。 代码尝试从文件读取,但引发FileException。该方法捕捉 FileException 并引发 BadFormatException。在此情况下,FileException 可保存在 BadFormatException 的 InnerException 属性中。
为提高调用方确定异常引发原因的能力,有时可能需要方法捕捉帮助器例程引发的异常,然后引发一个进一步指示已发生的错误的异常。 可以创建一个更有意义的新异常,其中内部异常引用可以设置为原始异常。 然后可以针对调用方引发这种更有意义的异常。 请注意,使用此功能,可以创建以最先引发的异常作为结束点的一系列相链接的异常。
·StackTrace 属性
此属性包含可用来确定错误发生位置的堆栈跟踪。如果有可用的调试信息,则堆栈跟踪包含源文件名和程序行号。
·Data 属性:此属性是可以保存任意数据(以键值对的形式)的IDictionary。
异常的处理方式
先执行try里面的语句,如果try里面的语句抛出了错误,就会被catch捕获,所以就会中断try里面语句的执行转而执行catch里面的语句,如果try里面的语句都执行完了也没有抛出错误,那么catch里的语句就没有机会执行了。最后不论try顺利运行完毕,还是try抛出了错误被catch语句捕获并执行了catch的语句都要接着执行finally里面的语句。
发生异常时,系统将搜索可以处理该异常的最近的 catch 子句(根据该异常的运行时类型来确定)。首先,搜索当前的方法以查找一个词法上包含着它的 try 语句,并按顺序考察与该 try 语句相关联的各个 catch 子句。如果上述操作失败,则在调用了当前方法的方法中,搜索在词法上包含着当前方法调用代码位置的 try 语句。此搜索将一直进行下去,直到找到可以处理当前异常的 catch 子句(该子句指定一个异常类,它与当前引发该异常的运行时类型属于同一个类或是该运行时类型所属类的一个基类)。注意,没有指定异常类的 catch 子句可以处理任何异常。找到匹配的 catch 子句后,系统将把控制转移到该 catch 子句的第一条语句。在 catch 子句的执行开始前,系统将首先按顺序执行嵌套在捕捉到该异常的 try 语句里面的所有 try 语句所对应的全部 finally 子句。
如果没有找到匹配的 catch 子句,则发生下列两种情况之一:
· 如果对匹配的 catch 子句的搜索到达一个静态构造函数或静态字段初始值设定项,则在导致调用该静态构造函数的代码位置引发 System.TypeInitializationException。该 System.TypeInitializationException 的内部异常将包含最初引发的异常。
· 如果对匹配的 catch 子句的搜索到达最初启动当前线程的代码处,则该线程的执行就会终止。此类终止会产生什么影响,应由实现来定义。
特别值得注意的是在析构函数执行过程中发生的异常。如果在析构函数执行过程中发生异常且该异常未被捕获,则将终止该析构函数的执行,并调用它的基类的析构函数(如果有)。如果没有基类(如 object 类型中的情况),或者如果没有基类析构函数,则该异常将被忽略。
异常类的层次结构
System.ArrayTypeMismatchException:当存储一个数组时,如果由于被存储的元素的实际类型与数组的实际类型不兼容而导致存储失败,就会引发此异常。
System.DivideByZeroException:在试图用零除整数值时引发。
System.IndexOutOfRangeException:在试图使用小于零或超出数组界限的下标索引数组时引发。
System.InvalidCastException:当从基类型或接口到派生类型的显式转换在运行时失败时引发。
System.NullReferenceException:在需要使用引用对象的场合,如果使用 null 引用时引发。
System.OutOfMemoryException:在分配内存(通过 new)的尝试失败时引发。
System.OverflowException:在 checked 上下文中的算术运算溢出时引发。
System.StackOverflowException:当执行堆栈由于保存了太多挂起的方法调用而耗尽时,就会引发此异常;这通常表明存在非常深或无限的递归。
System.TypeInitializationException:在静态构造函数引发异常并且没有可以捕捉到它的 catch 子句时引发。
产生TypeInitializationException的情况就包含以下几种:
例如访问ClassHelper.StaticString,由于静态成员Field的初始化产生异常,因此调用ClassHelper.StaticString会抛出TypeInitializationException。
例如访问ClassHelper.Field。
例如ClassHelper helper = new ClassHelper()。
异常处理准则
4. 可以在非最上层抛出自定义异常。如果是自定义异常,请保证其是可序列化的,并且保证其实现了Exception的三个构造函数。自定义异常不要继承Exception基类。相反,继承ApplicationException
5.异常的抛出与截获需要很多的CPU时间,不要在所有的方法中写的try - catch。只在有可能有某个特定的异常发生的方法中使用它。
6.始终捕获特定的异常,而不是一般的异常和系统异常。
7.当发生异常时,为了确保清理占据的资源,使用try / finally块。在finally子句中关闭的资源。使用try / finally块,即使发生异常,也能确保资源disposed。
8.在一个catch块中的代码都应该至少部分地处理了所捕捉的异常。否则,就不要使用catch块。
9.从构造函数中抛出异常。因为构造函数没有返回值,所以没有简单的方法来想构造函数的调用者发出构造失败的信号,这时便可以通过抛出异常来做到。比如构造参数与指定条件不符时,就抛出一个异常。
10.在以上前提的保证下,可以在非最上层使用AOP截获(intercept)异常而进行日志记录,这样通过日志记录,我们可以了解系统的运行状态。也可以有一个应用程序级(线程级)的错误处理程序,您可以用它处理所有一般异常。在一个'意外一般错误'中,这个错误处理程序应该捕获该异常并记录他,除此之外,在应用程序关闭之前应该做出友好的信息提示或者允许用户选择忽略异常继续。
记不起在哪里看到过这样一句话:在软件实现中,异常和日志都是重要的质量保证手段,异常和日志总是同时出现的。可以说,异常是日志记录的重要/主要组成部分。
调试
使用断点进入调试模式。