目录:数据库
20.1 定义“异常”编程
20.2 异常处理机制数组
20.3 System.Exception类闭包
20.4 FCL定义的异常类异步
20.5 抛出异常编程语言
20.6 定义本身的异常类异步编程
20.7 用可靠性换取开发效率性能
20.8 设计规范和最佳实践测试
20.9 未处理的异常线程
20.10 对异常进行调试
20.11 异常处理的性能问题
20.12 约束执行区域(CLR)
20.13 代码协定
异常指成员没有完成它的名称所宣称的行动。
经过异常处理返回错误报告。
2.1 try块:若是代码须要执行通常性的资源清理操做,须要从异常中恢复,或者二者都须要,就能够放到try块中。负责清理的代码应放到一个finally块中。try块还可包含也许会抛出异常的代码。负责异常恢复的代码应放到一个或多个catch块中。
2.2 catch块:catch块包含的是响应一个异常须要执行的代码。catch关键字后的圆括号中的表达式称为捕捉类型。C#要求捕捉类型必须是System.Exception或者它的派生类型。
在catch块末尾,有三种选择:
1.重新抛出相同的异常,向调用栈高一层的代码通知该异常的发生。
2.抛出一个不一样的异常,向调用栈高一层的代码提供更丰富的异常信息。
3.让线程从catch块的底部退出。
2.3 finally块:finally块包含的是保证会执行的代码。通常在finally块中执行try块的行动所要求的资源清理操做。
CLR容许异常抛出任何类型的实例——从Int32到String均可以。可是,Microsoft决定部强迫全部编程语言都抛出和捕捉任意类型的异常。所以,他们定义了System.Exception类型,并规定全部CLS相容的编程语言都必须能抛出和捕捉派生自该类型的异常。
属性名称 | 访问 | 类型 | 说明 |
Message | 只读 | String | 包含辅助性文字说明,指出抛出异常的缘由。 |
Data | 只读 | IDictionary | 引入一个“键/值对”集合。一般,代码在抛出异常前在该程序集合中添加记录项;捕捉异常的代码可在异常恢复中查询记录项并利用其中的信息。 |
Source | 读/写 | String | 包含生成异常的程序集的名称 |
StackTrace | 只读 | String | 包含抛出异常以前调用过的全部方法的名称和签名,该属性对调试颇有用。 |
TargetSite | 只读 | String | 包含抛出异常的方法 |
HelpLink | 只读 | String | 包含帮助用户理解异常的一个文档的URL |
InnerException | 只读 | Exception | 若是当前异常是在处理一个异常时抛出的,该属性就指出上一个异常时什么。 |
HResult | 读/写 | Int32 | 跨越托管和本机代码边界时使用的一个32位值。 |
抛出异常时,CLR会重置异常起点;也就是说,CLR只记录最新的异常对象抛出位置。
实现本身得方法时,若是方法没法完成方法名所指明得任务,就应抛出一个异常。
从Excetion派生的全部类型都应该时可序列化的,使它们能穿越AppDomain边界或者写入日志/数据库。
面向对象编程极大提高了开发人员的开发效率。开发效率的提高很大一部分来自可组合性,它使代码容易编写,阅读和维护。
除了代码的可组合性,开发效率的提高还来自编译器提供的各类好用的功能。
调用方法时插入可选参数
对值类型的实例进行装箱
构造/初始化参数数组
绑定到dynamic变量/表达式的成员
绑定到扩展方法。
绑定/调用重载的操做符(方法)
构造委托对象。
在调用泛型方法,声明局部变量和使用lambda表达式时推断类型。
为lambda表达式和迭代器定义/构造闭包类。
定义/构造/初始化匿名类型及其实例
重写代码来支持LINQ查询表达式和表达式树
另外,CLR自己也提供大量辅助来进一步简化编程:
调用虚方法和接口方法。
加载程序集并对方法进行JIT编译,会抛出异常。
访问MarshalByRefObject派生类型的对象时穿越AppDomain边界。
调用Thread.Abort或AppDomain.Unload时形成线程抛出ThreadAbortException。
垃圾回收以后,在回收对象的内存以前调用Finalize方法。
使用泛型类型时,在Loader堆中建立类型对象。
抛出各类异常。
为了缓解对状态的破坏,能够:
执行catch或finally块中的代码时,CLR不容许线程终止。
能够用System.Diagnostics.Contracts.Contract类向方法应用代码协定
可使用约束执行区,它能消除CLR的某些不去肯定性。
取决于状态存在于何处,可利用事务来确保状态要么都修改,要么都不修改。
将本身的方法设计得更明确。
8.1 善用finally块
确保清理很重要,因此编程语言提供了一些构造来简化代码编写:
使用lock语句时,锁在finally块中释放。
使用using语句时,在finally块中调用对象的Dispose方法。
使用foreach语句时,在finally块中调用IEnumerator对象的Dispose方法。
定义析构器方法时,在finally块中调用基类的Finalize方法。
使用这些构造时,编译器将你写的代码放到try块内部,并将清理代码放到finally块中。
8.2 不要什么都捕捉
应用程序代码抛出异常,应用程序的另外一部分可能预期要捕捉该异常。因此,不要写“大小通吃”的类型,悄悄地吞噬异常,而是应该容许异常在调用栈中向上移动,让应用程序代码针对性地处理它。
若是异常未获得处理,CLR会终止进程。也能够在一个线程中捕捉异常,在另外一个线程中从新抛出异常。为此提供支持的是异步编程模型。
8.3 得体地从异常中恢复
捕捉具体异常时,应充分掌握在何时会抛出异常,并知道从捕捉的异常类型派生出了哪些类型。
8.4 发生不可恢复的异常时回滚部分完成的造做——维持状态
8.5 隐藏实现细节来维持协定
异常抛出时,CLR在调用栈中向上查找与抛出的异常对象的类型匹配的catch块。没有任何catch块匹配抛出的异常类型,就发生一个未处理的异常。CLR检测到进程中的任何未处理的异常,都会终止进程。未处理异常代表应用程序遇到了未预料到的状况,并认为这是应用程序的真正bug。
异常处理的代价:
非托管C++编译器必须生成代码来跟踪哪些对象被成功构造。编译器还必须生成代码,以便在一个异常被捕捉到的时候,调用每一个已构造的对象的析构器。
托管编译器就要轻松得多,由于托管对象在托管堆中分配,而托管堆受垃圾回收器监视。如对象成功构造,并且抛出了异常,垃圾回收器最终会释放对象得内存。
在CLR中,咱们有包含了状态的AppDomain。AppDomain卸载时,它的全部状态都会卸载。全部,若是AppDomain中的一个线程遭遇未处理的异常,能够在不终止整个进程的状况下卸载AppDomain。
根据定义,CER是必须对错误有适应力的代码块。因为AppDomain可能被卸载,形成它的状态被销毁,因此通常用CER处理由多个AppDomain或进程共享的状态。若是要在抛出了非预期的异常时维护状态,CER就很是有用。有时将这些异常称为异步异常。
代码协定提供了直接在代码中声明代码设计决策的一种方式:
前条件:通常用于对实参进行验证。
后条件:方法由于一次普通的返回或者抛出异常而终止时,对状态进行验证。
对象不变性:在对象整个生命期内,确保对象的字段的良好状态。
代码协定有利于代码的使用,理解,进化,测试,文档和早期错误检测。可将前条件,后条件和对象不变性想象为方法签名的一部分。