C#系列文章之异常
用一段代码说明异常处理的用法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public void SomeMethod() 2 { 3 try 4 { 5 //需要得体的进行恢复或清理的代码 6 } 7 catch(InvalidOperationException) 8 { 9 //需要从InvalidOperationException恢复的代码 10 } 11 catch(IOException) 12 { 13 //需要从IOException恢复的代码 14 } 15 catch 16 { 17 //除了上述异常,其他所有异常恢复的代码放在这里 18 //通常需要重新抛出 19 throw; 20 } 21 finally 22 { 23 //对始于try块中的任何操作进行清理 24 //总是执行,不管是不是抛出异常 25 } 26 //如果try没有抛出异常,或者某个catch块捕捉到异常,但没有抛出,就会继续执行下面的代码 27 }
需要说明的是C#1.0版本和C#2.0版本,如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public void SomeMethod2() 2 { 3 try 4 { 5 6 } 7 catch(Exception ex) 8 { 9 //C#2.0以前,这个块只能捕捉CLS相容的异常,之后的版本相容不相容都可以捕获 10 //所有面向CLR的编程语言都必须支持从Exception派生的对象,这是CLS硬性规定 11 //但是有些语言还允许非Exception派生的对象,这就是CLS不相容。 12 } 13 catch 14 { 15 //所有的版本都可以在此捕捉到CLS相容与不相容异常 16 } 17 }
自定义异常:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #region 封装异常类 2 [Serializable] 3 public abstract class ExceptionArgs 4 { 5 public virtual string Message { get { return string.Empty; } } 6 } 7 [Serializable] 8 public class Exception<TExceptionArgs> : Exception, ISerializable where TExceptionArgs : ExceptionArgs 9 { 10 private const string c_args = "Args"; 11 private readonly TExceptionArgs m_Args; 12 13 public TExceptionArgs Args { get { return m_Args; } } 14 15 public Exception(TExceptionArgs args, string message = null, Exception innerException = null) : base(message, innerException) { m_Args = args; } 16 17 /// <summary> 18 /// 用于反序列化 19 /// </summary> 20 /// <param name="info"></param> 21 /// <param name="context"></param> 22 [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] 23 private Exception(SerializationInfo info, StreamingContext context) 24 : base(info, context) 25 { 26 m_Args = (TExceptionArgs)info.GetValue(c_args, typeof(TExceptionArgs)); 27 } 28 /// <summary> 29 /// 用于序列化 30 /// </summary> 31 /// <param name="info"></param> 32 /// <param name="context"></param> 33 [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] 34 public override void GetObjectData(SerializationInfo info, StreamingContext context) 35 { 36 info.AddValue(c_args, m_Args); 37 base.GetObjectData(info, context); 38 } 39 public override string Message 40 { 41 get 42 { 43 string baseMsg = base.Message; 44 return m_Args == null ? baseMsg : baseMsg + "(" + m_Args.Message + ")"; 45 } 46 } 47 48 public override bool Equals(object obj) 49 { 50 Exception<TExceptionArgs> other = obj as Exception<TExceptionArgs>; 51 if (other == null) return false; 52 return Object.Equals(m_Args, other.m_Args) && base.Equals(obj); 53 } 54 public override int GetHashCode() 55 { 56 return base.GetHashCode(); 57 } 58 59 } 60 #endregion 61 62 #region 使用 63 /// <summary> 64 /// 定义异常类型 65 /// </summary> 66 public sealed class DiskFullExceptionArgs : ExceptionArgs 67 { 68 private readonly string m_diskpath; 69 public DiskFullExceptionArgs(string diskpath) { m_diskpath = diskpath; } 70 71 //返回只读属性 72 public string DiskPath { get { return m_diskpath; } } 73 74 public override string Message 75 { 76 get 77 { 78 return (m_diskpath == null) ? base.Message : "DiskPath=" + m_diskpath; 79 } 80 } 81 } 82 83 public void Test_DiskFullExceptionArgs() 84 { 85 try 86 { 87 throw new Exception<DiskFullExceptionArgs>(new DiskFullExceptionArgs(@"C:\"), "the disk is full"); 88 } 89 catch(Exception<DiskFullExceptionArgs> e) 90 { 91 Console.WriteLine(e.Message); 92 } 93 } 94 95 #endregion
关于异常处理的某些实践性建议:
- final 块很实用,要善于利用。某些语句使得C#编译器自动生成try/finally块。
- 使用lock语句。锁在finally自动释放。
- 使用using语句,在finally块中调用对象Dispose方法。
- 使用foreach语句,在finally块中调用IEnumerator对象的Dispose方法。
- 定义析构器方法,在finally块中调用基类的Finalize方法。
- 不要什么都捕捉。捕捉异常表明你预见到该异常,理解他为什么发生,并知道如何处理,而不是不管不顾。
- 不要捕捉Exception异常,即使捕捉也要将他重新抛出(throw),否则会使应用程序忽略错误继续运行,造成不可预测的结果和潜在的安全隐患。
- 得体的进行恢复:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public string CalculateSpreadsheetCell(int row,int column) 2 { 3 string result; 4 try 5 { 6 result = /*电子表格单元格中的值*/; 7 } 8 catch(DivideByZeroException) 9 { 10 //单元格中内容有可能是某两个单元格相除结果,在此处理除数为0的情况 11 result = "Can't show value: Divide by zero"; 12 } 13 catch(OverflowException) 14 { 15 //单元格中内容有可能是某两个单元格相相乘的结果,在此处理结果溢出的情况 16 result = "Can't show value:Too big"; 17 } 18 return result; 19 }
- 发生不可恢复的异常时回滚部分完成的操作---维持状态。例如可以首先记录原始状态,当出现异常时,在catch中捕获并将状态恢复到原始状态。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public void SerializeObjectGraph(FileStream fs,IFormatter formatter,Object rootObject) 2 { 3 Int64 beforeSerialization = fs.Position; 4 5 try 6 { 7 //将对象图序列化到文件中 8 formatter.Serialize(fs, rootObject); 9 } 10 catch//捕捉所有异常 11 { 12 //放在此处说明只有在失败时才进行重置。 13 fs.Position = beforeSerialization; 14 fs.SetLength(fs.Position); 15 16 //重新抛出异常,让调用者知道发生了什么 17 throw; 18 } 19 }
- 需要的话可以捕捉一个异常,而抛出不同的异常。慎用,除非你充分掌握抛出异常的原因。如果你的目的是在异常中添加额外的数据,只需在Data属性中添加数据,然后重新抛出相同的异常。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public void SomeMethod3(string filename) 2 { 3 try 4 { 5 /*......*/ 6 } 7 catch(IOException e) 8 { 9 e.Data.Add("FileName", filename); 10 throw; 11 } 12 }
未处理异常
类库开发人员用不着去考虑未处理的异常,只有应用程序的开发人员才关心未处理异常。所谓未处理异常是指异常抛出时,CLR在调用栈向上查找培训的catch块,没有找到就会发生未处理异常,也就是说你没对异常进行有效的恢复或处理。
应用程序发生未处理异常时,Windows会向事件日志写一条记录,通过事件查看器->Windows日志->应用程序节点查看。windows操作中心也有许多细节可查。
对异常进行调试:
在Visual Studio工具中选择调试->异常会列出识别的不同种类的异常。如果勾选了Thrown选项框,调试器会在遇到该异常时中断,不管你有没有进行处理。这对于调试和确定异常的位置来说很有帮助。通常来说是不勾选的,此时只会在遇到未处理的异常时调试器才会中断,如果你进行了处理,调试器就会明白此处不需要进行中断。
约束执行区域(CER)和 代码协定(code contract)(不做深入研究)
约束执行区域(CER):CER是必须对错误有适应力的代码块。由于AppDomain可能被卸载,造成他的状态被销毁,所以一般用CER处理有多个AppDomain或进程共享的状态。
代码协定(code contract):提供了直接在代码中声明代码设计决策的一种方式。
- 前条件,一般用于对实参进行验证
- 后条件,方法因为一次普通的返回或者抛出异常而终止时,对状态进行验证。
- 对象不变形,在对象的整个生命期内,确保对象字段的良好状态。