Ryan.Sun

Love is everything

导航

.NET异常系统——认识异常

Posted on 2010-11-09 00:56  Ryan.Sun  阅读(614)  评论(2编辑  收藏  举报

异常是编写程序时经常碰到的一个类型,经验告诉我们,异常处理是一项细致的工作,代码量往往比正常逻辑代码更多更复杂。

异常是程序运行时发生的,比如连接远程服务获取数据超时等;异常与错误不同,前者是发生在运行时,函数尚未执行完毕退出,而后者则是函数已经执行完毕并正常退出,两者相同的是函数都没有获得预期的结果。

 

.NET的框架不像C语言需要使用特殊方法才能支持异常处理,而是在语言层面就通过try,catch和finally实现了异常处理。例如:

捕捉异常
 1 try {
 2 // Put code requiring graceful recovery and/or cleanup operations here...
 3 }
 4 catch (InvalidOperationException) {
 5 // Put code that recovers from an InvalidOperationException here...
 6 }
 7 catch (IOException) {
 8 // Put code that recovers from an IOException here...
 9 }
10 catch {
11 // Put code that recovers from any kind of exception other than those above here...
12 // When catching any exception, you usually re-throw the exception.
13 // I explain re-throwing later in this chapter.
14 throw;
15 }
16 finally {
17 // Put code that cleans up any operations started within the try block here...
18 // The code in here ALWAYS executes, regardless of whether an exception is thrown.
19 }

 

.NET框架中异常处理机制采用类似事件冒泡的方法,如果函数发生了异常,首先被CLR截获,然后根据异常类型匹配catch代码块,如果匹配失败则由CLR调用Environment.FailFast结束当前进程,如果匹配成功则执行catch代码块,最后再执行finally语句块。

 

在C#中,所有的异常类型都继承自System.Exception,这就意味着catch代码必须按照类型的继承关系,从子类型到父类型按顺序编写,否则子类型的异常就会匹配执行到其父类型的catch语句。catch是用来发生异常后恢复状态或清理的,这部分代码要尽量简单且不会引发异常,否则catch中的异常将同样被CLR获取,有可能成为未处理异常向外抛出,导致CLR调用Environment.FailFast结束当前进程。finally同理亦然。

 

System.Exception包括了如下属性

Message:用来描述异常信息

Source: 用来描述程序集名称

StackTrace:用来显示运行时调用树

TargetSite:用来显示发生异常的方法

InnerException: 用来显示内部的异常

 

C#中关于异常的另一个关键字是throw,用来主动向CLR抛出异常,与Exception的StackTrace和TargetSite有关系,如以下程序

throw异常
 1         static void Main()
 2         {   
 3             try
 4             {
 5                 throwExpReset();
 6             }
 7             catch (Exception ex)
 8             {
 9                 Console.WriteLine(ex.StackTrace);
10                 Console.WriteLine(ex.TargetSite);
11             }
12 
13             try
14             {
15                 throwExpWithoutReset();
16             }
17             catch (Exception ex)
18             {
19                 Console.WriteLine(ex.StackTrace);
20                 Console.WriteLine(ex.TargetSite);
21             }
22 
23             Console.ReadLine();
24         }
25 
26         private static void throwExpReset()
27         {
28             try
29             {
30                 int i = 1;
31                 int j = 0;
32                 int k = someMethod(i, j);
33             }
34             catch (Exception ex)
35             {
36                 throw ex;
37             }
38         }
39 
40         private static void throwExpWithoutReset()
41         {
42             try
43             {
44                 int i = 1;
45                 int j = 0;
46                 int k = someMethod(i, j);
47             }
48             catch (Exception ex)
49             {
50                 throw;
51             }
52         }
53 
54         private static int someMethod(int i, int j)
55         {
56             return i / j;
57         }
58 

 

上面程序不同之处在于一个方法只调用throw另一个调用throw异常,在DEBUG环境下,throwExpWithoutReset能够输出发生异常的方法名someMethod,而throwExpReset则不能输出异常的方法,因为throw一个异常时会通知CLR更改异常开始点,进而影响了异常的StackTrace和TargetSite,而单个的throw则不会通知CLR更改异常开始点,不改变原始异常的各种信息。然而,当在RELEASE环境下,这两个方法却都不能输入产生异常的方法名,这是因为C#的JIT编译器在RELEASE环境下出于优化的目的会把一些方法inline编译,由于没有了方法名,也就无法从异常信息中获取。为了能够获取异常的“真实”信息,需要对someMethod增加属性强制编译器不inline编译此方法,如下所示

1 [MethodImpl(MethodImplOptions.NoInlining)]//forbit the JIT to inline method
2 private static int someMethod(int i, int j)
3 {
4     return i / j;
5 }

 

对于系统中未处理的异常,往往需要产生异常的方法和调用树用来还原产生异常时的环境,从而处理此异常,所以发生异常时又不能处理时,应该使用throw,除非需要抛出一个新异常,如果发现异常信息的粒度还不够细,则应考虑给方法属性增加防止编译器inline。