异常和状态管理
一、异常处理机制
1,应该在try中放置多少代码?
取决于状态管理。如果在一个try块中执行多个可能抛出同一个异常类型的操作,但不同的操作有不同的异常恢复措施,则应该将每个操作都放到他自己的try块中,这样才能正确地恢复状态
2,try、finally,catch执行顺序
try { try { throw new Exception("异常"); } finally { Console.WriteLine("finally"); } } catch { Console.WriteLine("catch"); } //输出顺序:finally catch Console.ReadLine();
3,finally设计
private void ReadData(string pathname) { FileStream fs = null; try { fs = new FileStream(pathname, FileMode.Open); //处理文件中的数据 } catch (IOException) { //在此添加从IOException恢复的代码 } finally { //确保文件被关闭 if(fs!=null)fs.Close(); } }
二、System.Exception类
属性名称 |
访问 |
类型 |
说明 |
Message |
只读 |
String |
包含辅助性文字说明,指出抛出异常的原因。 如果抛出的异常未处理,该消息通常被写入日志。 由于最终用户一般不看这种消息,所以消息应提供尽可能多的技术细节,方便开发人员修正代码 |
Data |
只读 |
IDictionary |
引用一个“键/值对”集合。 代码在抛出异常前在该集合中添加记录项;捕捉异常的代码可在异常恢复过程中查询记录项并利用其中的信息 |
Source |
读/写 |
String |
包含生成异常的程序集名称 |
StackTrace |
只读 |
String |
包含抛出异常之前调用过的所有方法的名称和签名,该属性对调试很有用 |
TargetSite |
只读 |
MethodBase |
包含抛出异常的方法 |
HelpLink |
只读 |
String |
包含帮助用户理解异常的一个文档的URL(例如file://c:/myapp/help.html#MyExceptionHelp)。但要注意,健全的编程和安全实践阻止用户查看原始未处理的异常。因此,除非希望将信息传达给其他程序员,否则不要使用该属性 |
InnerException |
只读 |
Exception |
如果当前异常是在处理一个异常时抛出,该属性就指出上一个异常是什么。 这个属性通常为null。 Exception类型还提供了公共方法GetBaseException来遍历由内层构成的链表,并返回最初抛出的异常 |
HResult |
读/写 |
Int32 |
托管代码喝本机代码边界时使用的一个32位值。 例如:当COM API返回代表失败的HRESULT值时,CLR抛出一个Exception派生对象,并通过该属性来维护HRESULT值 |
//禁止JIT编译器在调试喝发布生成时对该方法进行内联处理 [MethodImpl(MethodImplOptions.NoInlining)] private void aa() { }
三、设计规范和最佳实践
1,善用finally块
①无论线程抛出什么类型的异常,finally块中的代码都会执行
②应该先用finally块清理那些已成功启动的操作,再返回至调用者执行finally块之后的代码
③需经常利用finally块显式释放对象以避免资源泄露
只要使用了lock、using、foreach语句、重写类的析构器时c#编译器也会自动生成try/finally块。使用这些构造时,编译器将你写的代码放到try块内部,并将清理代码放到finally块中
①使用lock语句时,锁在finally块中释放
②使用using语句时,在finally块中调用对象的Dispose方法
③使用foreach'语句时,在finally快中调用IEnumerator对象的Dispose方法
④定义析构方法时,在finally块中调用基类的Finalize方法
2,不要什么都捕捉
3,得体地从异常中恢复
public string CalculateSpreadsheetCell(int row, int column) { string result; try { result = /*计算电子表格单元格中的值*/ } catch (DivideByZeroException) //捕捉被零整除错误 { result = "can't show value:divide by zero"; } catch (OverflowException)//捕捉溢出错误 { result = "can't show value:too big"; } return result; }
4,发生不可恢复的异常时回滚部分完成的操作——维持状态
public void SerializeObjectGraph(FileStream fs, IFormatter formatter, object rootObj) { //保存文件的当前位置 Int64 beforeSerializetion = fs.Position;//获取或设置此流的当前位置 try { //尝试将对象图序列化到文件中 formatter.Serialize(fs, rootObj); } catch //捕捉所有异常 { //任何事情出错,就将文件恢复到一个有效的状态 fs.Position = beforeSerializetion; //截断文件 fs.SetLength(fs.Position); //注意:上述代码没有放到finally快中,因为只有在序列化失败时才对流进行重置 //重新抛出相同的异常,让调用者知道发生了什么 throw; } }
5,异常实现细节来维系协定
private string m_pathname;//地址簿文件的路径名 public string GetPhoneNumber(string name) { string phone; FileStream fs = null; try { fs = new FileStream(m_pathname, FileMode.Open); //这里的代码重fs读取内容,直至找到匹配的name phone = /*已经找到的电话号码*/ } catch (FileNotFoundException e) { //抛出一个不同的异常,将name包含到其中,并将原来的异常设为内部异常 throw new NameNotFoundException(name, e); } catch (IOException e) { //抛出一个不同的异常,将name包含到其中,并将原来的异常设为内部异常 throw new NameNotFoundException(name, e); } finally { if(fs!=null)fs.Close(); } return phone; }
通过反射调用方法时,CLR内部捕捉方法抛出的任何异常,并把它转换成一个TargetInvocationException
四、未处理的异常
1,异常抛出时,CLR在调用栈中向上查找与抛出的异常对象类型匹配的catch块。没有任何catch块匹配抛出的异常类型,就发生一个未处理的异常。CLR检测到继承中的任何线程有未处理的异常,都会禁止进程
2,类库开发人员不用去处理未处理的异常,应用程序开发人员需要处理,Micorosoft建议应用程序开发人员接受CLR默认策略(应用程序发生未处理的异常时,windows会想事件日志写入一条记录,通过windows日志->应用程序 查看)
五、约束执行区域(CER)
根据定义,CER是必须对错误有适应力的代码块
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { //强迫finally块中的代码提前准备好 RuntimeHelpers.PrepareConstrainedRegions();//using System.Runtime.CompilerServices; try { Console.WriteLine("In Try"); } finally { //隐士调用Type1的静态构造器(调用S方法不会先输出.ctor exception) Type1.M(); } Console.ReadLine(); //输出:.ctor exception //In Try } } public sealed class Type1 { static Type1() { //如果这里抛出异常,M就得不到调用 Console.WriteLine(".ctor exception"); } //using System.Runtime.ConstrainedExecution; [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static void M() { } public static void S() { } } }
1,PrepareConstrainedRegions是一个很特别的方法。
①JIT编译器如果发现在一个try块之前调用了这个方法,就会提前编译与try关联的catch和finally块中的代码。
②JIT编译器会加载任何程序集,创建任何类型对象,调用任何静态构造器,并对任何方法进行JIT编译
③如果其中任何操作造成异常,这个异常会在线程假如try块之前发生
④JIT编译器提前准备方法时,会遍历整个调用图,提前准备被调用的方法,前提是这些方法应用了ReliabilityContractAttribute(并且传递的是Consistency.WillNotCorruptState或者Consistency.MayCorruptInstance)
⑤如果你写的方法保证不损坏任何状态,就用Consistency.WillNotCorruptState,否则就用其他三个值之一来申明方法可能损坏哪一种状态
⑥如果方法保证不会失败,就用Cer.Success,否则用Cer.MayFail(Cer.None这个值表明方法不就行CER保证)