try...catch...finally中的finally一定会执行吗?(二,完结篇)

今天早上匆匆写了篇文章,上班时间也找了找资料,也没找到任何有效性的说明,所以还得自己来。

还是先上一段测试代码:

static void Foo()
    {
        Console.WriteLine(
"enter Foo");
        
try { Console.WriteLine("enter Foo try"); return; }
        
finally { Console.WriteLine("enter Foo finally"); }
    }
    
static void Main(string[] args)
    {
        Console.WriteLine(
"before enter Foo");
        Foo();
        Console.WriteLine(
"after enter Foo");
    }

比较简单,就是在不同的时候打印几个字符串。

我看了看IL,在Foo 的代码里发现了一个特殊的指令,用ILDASM dump出IL:

 

.method private hidebysig static void  Foo() cil managed
  {
    
// Code size       42 (0x2a)
    .maxstack  1
    
IL_0000:  nop
    
IL_0001:  ldstr      "enter Foo"
    
IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
    
IL_000b:  nop
    .try
    {
      
IL_000c:  nop
      
IL_000d:  ldstr      "enter Foo try"
      
IL_0012:  call       void [mscorlib]System.Console::WriteLine(string)
      
IL_0017:  nop
      
IL_0018:  leave.s    IL_0028

    }  
// end .try
    finally
    {
      
IL_001a:  nop
      
IL_001b:  ldstr      "enter Foo finally"
      
IL_0020:  call       void [mscorlib]System.Console::WriteLine(string)
      
IL_0025:  nop
      
IL_0026:  nop
      
IL_0027:  endfinally
    }  
// end handler
    IL_0028:  nop
    
IL_0029:  ret
  } 
// end of method Class1::Foo

 

leave.s IL_0028,这个指令是什么意思呢?查了查msdn:

 

leave.s 指令无条件将控制转移到传递的目标指令,这表示为距当前指令之后的指令的开始处的 1 字节有符号偏移量。 

leave.s 指令类似于 br 指令,但它可用于退出 try、filter 或 catch 块,而一般分支指令只能在此类块中使用以在其内部转移控制。leave.s 指令清空计算堆栈并确保执行周围适当的 finally 块。 

不能使用 
leave.s 指令退出 finally 块。为了简化异常处理程序的代码生成,一个有效的方法是在 Catch 块的内部使用 leave.s 指令将控制转移到关联的 try 块中的任何指令。 

如果指令有一个或多个前缀代码,则只能将控制转移到其中的第一个前缀。 

 

看了看,也不是很明白,到底是怎么执行呢?我想到了一个Tool,sharpdevelop,一个开源的IDE,记得好像能调式IL,

下了一个安装,安下断点,调式发现leave.s实际上执行finally里的代码,然后再到IL_0028;我试着将IL_0028改为

IL_0029,结果同样执行完finally之后则跳到了IL_0029.

将IL_0028改为IL_000d,则没有执行finally里的代码,在IL_000d和IL_0018之间形成了死循环,不停的打印字符串“enter Foo try”。

将IL_0028改为IL_0030,则报错。

这时候大家可能就明白了,leave.s target其实就是执行其后的finally代码块然后跳到target处。

能保证finally一定执行的秘诀就在LEAVE指令。


 

在这里再补充一点吧。免得大家看了没什么收获,其实方法都告诉大家了,可以自己去尝试啊,自己动手才能记得牢啊。

代码改为:

 

 1 static void Foo()
 2     {
 3         Console.WriteLine("enter Foo");
 4         try
 5         {
 6             Console.WriteLine("enter Foo try");
 7             try
 8             {
 9                 Console.WriteLine("enter Foo try1");
10                 throw null;
11             }
12             catch { Console.WriteLine("enter Foo catch1"); return; }
13             finally { Console.WriteLine("enter Foo finally1"); }
14 
15             Console.WriteLine("leav Foo try..catchfinally1");
16 
17             throw null;
18         }
19         catch { Console.WriteLine("enter Foo catch"); return; }
20         finally { Console.WriteLine("enter Foo finally"); }
21 
22         Console.WriteLine("leav Foo try..catchfinally");
23     }
24     static void Main(string[] args)
25     {
26         Console.WriteLine("before enter Foo");
27         Foo();
28         Console.WriteLine("after enter Foo");
29     }

大家能看出打印结果是什么吗?(leave写错了,大家请忽略)

看到了leave语句一个没有执行,但是嵌套的finally都执行了,这是为什么呢?

看Foo的IL:

 

 1 .method private hidebysig static void  Foo() cil managed
 2 {
 3   // Code size       98 (0x62)
 4   .maxstack  1
 5   IL_0000:  nop
 6   IL_0001:  ldstr      "enter Foo"
 7   IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
 8   IL_000b:  nop
 9   .try
10   {
11     .try
12     {
13       IL_000c:  nop
14       IL_000d:  ldstr      "enter Foo try"
15       IL_0012:  call       void [mscorlib]System.Console::WriteLine(string)
16       IL_0017:  nop
17       .try
18       {
19         .try
20         {
21           IL_0018:  nop
22           IL_0019:  ldstr      "enter Foo try1"
23           IL_001e:  call       void [mscorlib]System.Console::WriteLine(string)
24           IL_0023:  nop
25           IL_0024:  ldnull
26           IL_0025:  throw
27         }  // end .try
28         catch [mscorlib]System.Object 
29         {
30           IL_0026:  pop
31           IL_0027:  nop
32           IL_0028:  ldstr      "enter Foo catch1"
33           IL_002d:  call       void [mscorlib]System.Console::WriteLine(string)
34           IL_0032:  nop
35           IL_0033:  leave.s    IL_0060
36         }  // end handler
37       }  // end .try
38       finally
39       {
40         IL_0035:  nop
41         IL_0036:  ldstr      "enter Foo finally1"
42         IL_003b:  call       void [mscorlib]System.Console::WriteLine(string)
43         IL_0040:  nop
44         IL_0041:  nop
45         IL_0042:  endfinally
46       }  // end handler
47     }  // end .try
48     catch [mscorlib]System.Object 
49     {
50       IL_0043:  pop
51       IL_0044:  nop
52       IL_0045:  ldstr      "enter Foo catch"
53       IL_004a:  call       void [mscorlib]System.Console::WriteLine(string)
54       IL_004f:  nop
55       IL_0050:  leave.s    IL_0060
56     }  // end handler
57   }  // end .try
58   finally
59   {
60     IL_0052:  nop
61     IL_0053:  ldstr      "enter Foo finally"
62     IL_0058:  call       void [mscorlib]System.Console::WriteLine(string)
63     IL_005d:  nop
64     IL_005e:  nop
65     IL_005f:  endfinally
66   }  // end handler
67   IL_0060:  nop
68   IL_0061:  ret
69 // end of method Class1::Foo
70 
71 

 

在try1里引发了异常,其实有没有catch都一样,经IL的 leave.s指令执行finally,但是可以看到,不仅执行里面的finally1,而且连外面的finally也执行了。

我在<<CLR via C#>>里看到这方面的内容,原意大概是:

 

当嵌套的try-catch-finally内部发生异常时,首先会转到与之匹配的catch块,然后依次执行后面直至代码块末尾(方法的末尾)的位置之间的finally。

 

这样看来其实可以理解:leave指令会执行其后有层次关系finally,然后跳至函数的末尾,这点MSDN说的很含糊。

posted @ 2009-07-03 21:15  DiggingDeeply  阅读(2794)  评论(16编辑  收藏  举报