C#系列文章之异常

用一段代码说明异常处理的用法:

 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         }
View Code

需要说明的是C#1.0版本和C#2.0版本,如下:

 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         }
View Code

 

自定义异常:

 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
View Code

 

 

关于异常处理的某些实践性建议:

  • final 块很实用,要善于利用。某些语句使得C#编译器自动生成try/finally块。
  1. 使用lock语句。锁在finally自动释放。
  2. 使用using语句,在finally块中调用对象Dispose方法。
  3. 使用foreach语句,在finally块中调用IEnumerator对象的Dispose方法。
  4. 定义析构器方法,在finally块中调用基类的Finalize方法。
  • 不要什么都捕捉。捕捉异常表明你预见到该异常,理解他为什么发生,并知道如何处理,而不是不管不顾。 
  • 不要捕捉Exception异常,即使捕捉也要将他重新抛出(throw),否则会使应用程序忽略错误继续运行,造成不可预测的结果和潜在的安全隐患。
  • 得体的进行恢复:
 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         }
View Code
  • 发生不可恢复的异常时回滚部分完成的操作---维持状态。例如可以首先记录原始状态,当出现异常时,在catch中捕获并将状态恢复到原始状态。
 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         }
View Code
  • 需要的话可以捕捉一个异常,而抛出不同的异常。慎用,除非你充分掌握抛出异常的原因。如果你的目的是在异常中添加额外的数据,只需在Data属性中添加数据,然后重新抛出相同的异常。
 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         }
View Code

 

未处理异常

类库开发人员用不着去考虑未处理的异常,只有应用程序的开发人员才关心未处理异常。所谓未处理异常是指异常抛出时,CLR在调用栈向上查找培训的catch块,没有找到就会发生未处理异常,也就是说你没对异常进行有效的恢复或处理。

应用程序发生未处理异常时,Windows会向事件日志写一条记录,通过事件查看器->Windows日志->应用程序节点查看。windows操作中心也有许多细节可查。

 

对异常进行调试:

在Visual Studio工具中选择调试->异常会列出识别的不同种类的异常。如果勾选了Thrown选项框,调试器会在遇到该异常时中断,不管你有没有进行处理。这对于调试和确定异常的位置来说很有帮助。通常来说是不勾选的,此时只会在遇到未处理的异常时调试器才会中断,如果你进行了处理,调试器就会明白此处不需要进行中断。

 

约束执行区域(CER)和 代码协定(code contract)(不做深入研究)

 约束执行区域(CER):CER是必须对错误有适应力的代码块。由于AppDomain可能被卸载,造成他的状态被销毁,所以一般用CER处理有多个AppDomain或进程共享的状态。

代码协定(code contract):提供了直接在代码中声明代码设计决策的一种方式。

  • 前条件,一般用于对实参进行验证
  • 后条件,方法因为一次普通的返回或者抛出异常而终止时,对状态进行验证。
  • 对象不变形,在对象的整个生命期内,确保对象字段的良好状态。

 

posted @ 2017-09-03 13:16  贝同学  阅读(188)  评论(0编辑  收藏  举报