前言
这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享
Tips
- vs调试catch块时,监视窗口变量: $exception 查看当前抛出的异常对象
- 异常的catch是自上而下,回溯调用栈,如果未找到,就抛出未处理异常
- 异常的执行顺序:先执行body,再执行catch,最后执行finally
- 堆栈异常的执行顺序:ParentBody ChildBody ChildCatch ChildFinally ParentCatch ParentFinally
- .NET 4版本及后续版本中,可向AppDomain的FirstChanceException登记Appdomain中发生的第一次异常捕获,它发生在CLR搜索任何catch块之前
- finally中的代码保证会被执行,无论异常是否发生(用Win32函数TerminateThread杀死线程,或者用Winb32函数TerminateProcess或System.Environment的FailFast方法杀死进程,finally块不会执行。当然进程终止后,Windows会清理进程使用的所有资源)
- 如果catch或者finally中抛出异常,原有异常会丢失,新的异常继续向上回溯
- CLR只能捕获CLS相容(从Exception派生的异常被认为时CLS相容的)的异常,如果C#调用另一种编程语言的方法,抛出非CLS相容的异常,那么C#代码不能捕捉这个异常,这会有一些安全隐患
- 非CLS相容的一个异常被抛出时,CLR会自动构造RuntimeWrappedException类的实例,并初始化该实例的私有字段,使之引用实际抛出的对象。这样一来CLR就将非CLS相容的异常转变成了CLS相容的异常。所以任何能捕捉Exception类型的代码,都能捕捉非CLS相容的异常,从而消除了安全隐患
- 在C#2.0之后,Catch可以捕捉CLS相容和不相容的任何异常(Exception),而如果直接catch{} ,所以版本的C#都可以捕捉CLS相容和不相容异常
- 如果要兼容CLR旧的行为[assembly:RuntimeCompatibility(WrapNonExceptionThrows=false)]
- 一个异常抛出时,CLR会在内部记录throw指令的位置(抛出位置),一个catch捕捉到异常时,CLR又会记录异常的捕捉位置
- 在catch块内访问被抛出的异常对象的StackTrace属性,负责实现该属性的代码会调用CLR内部代码,创建一个字符串指出从异常抛出位置道异常捕捉位置的所有方法
- 在catch中重写throw e会重置异常的起点(重置堆栈的起点),不符合编码规范,如果仅仅使用throw 则OK
- 如果代码方法被内联,可能导致异常堆栈跟踪不准确对应到源代码,如果要防止JIT优化内联,使用编译器开关 /debug 或者[System.Runtime.CompilerServices.MethodImplAttribute(MethodImplOptions.NoInling)]
- 异常自身字符串消息可以包含详细的技术细节,用于跟踪调试,但这些消息不应该直接让应用层捕获,不应该向最终用户显示
- FCL的异常消息都使用了本地化字符串,开发人员可以根据实际情况考虑
- 设计和处理异常时,通常要牺牲可靠性来换取开发效率
- 尽量避免捕捉System.Exception然后允许应用程序继续允许,一个很大的问题是状态可能遭受破坏
- lock, using和foreach语句,都使用了try/finally块,重写析构器时,编译器也会自动生成try/finally
- 异步编程模型中异常会被“吞噬”然后重新抛出一样的异常
- 编码建议,一般捕获并处理异常之后,不要把它吞噬,单独使用throw,抛出相同的异常
- 关于异常的实践规范,可以好好思考一下,比如定义应用层异常、业务层异常、服务异常,通讯异常,然后将FCL的异常“吞噬”记录日志
- dynamic对象调用方法如果失败,会抛出TargetInvocationException异常。最初抛出的异常会正常地在调用栈中向上传递。这是使用C#的dynamic基元类型代替反射的一个很好的理由。(如果使用反射来调用方法,方法内的异常不能被正常捕获)
- 未捕获未处理的异常会写入Windows日志中
- 分布式程序中,尽量少的暴露异常信息,如果堆栈信息暴露,可能服务器中的信息被泄露
- 调试异常时,关注VS菜单“调试”-“异常”,可以强制引发某些异常发生,防止被“吞噬”(即使它被Catch)
- 另外异常处理的性能影响,能避免就避免,比如,如果有Try前缀的方法,尽量使用Try前缀的方法(不过Try的方法依然可能会抛异常,比如style参数无效,会抛出ArgumentException,另外还有可能抛出OutMemoryException异常)
- 如果要在抛出非预期的异常时维护状态,CER很有用。这种异常也称异步异常。CLR加载程序集时,在AppDomain的Load堆中创建一个类型对象,调用类型的静态构造器,并将IL代码JIT编译成本地代码。如果操作失败,CLR抛出异常报告失败。(很多隐式的非一致性的异常,导致无法预料)
- CER, PrepareConstrainedRegions很特别的方法,JIT编译器如果发现一个try块前调用这个方法,会提前编译与try关联的catch和finally块中的代码。JIT编译器会加载任何程序集,创建任何类型对象,调用任何静态构造器,并对任何方法JIT编译。如果其中任何操作造成异常,这个异常会在线程进入try块之前发生。JIT编译器提前准备方法时,还会遍历调用图,前提时方法一个用了[ReliabilityContractAttribute]而且传递Consistency.WillNotCorruptState或Consistency.MayCorruptInstance枚举成员。这是由于加入方法会损坏AppDomain或进程状态,CLR便无法对状态一致性做出保证
- 可以调用RuntimeHelper的PrepareMethod手动准备方法
- CER文章参考:http://www.cnblogs.com/Ninputer/archive/2006/06/30/439757.html
未捕获的异常处理
- Winform, OnThreadException虚方法及Application的ThreadException事件
- WPF程序Application的DispatcherUnhandledExceptions和System.WIndows.Therading.Dispatcher的UnhandledException和UnhandledExceptionFilter事件
- 对于SIlverlight,System.Window.Application的UnhandledExceptoin事件
- ASP.NET程序,System.Web.HttpApplication的Error事件
- 对于WCF,System.ServiceModel.Dispatcher.ChnnelDispatche的ErrorHandlers属性
契约式编程
有时间我好好整理一下这方面的东西,毕竟一种编程习惯的培养,需要慢慢积累去逐渐使用新的思维
参考文章:
http://www.infoq.com/cn/news/2009/02/Code-Contracts-.NET
http://www.cnblogs.com/lucifer1982/archive/2009/03/21/1418642.html