try...catch...finally中的finally一定会执行吗?(二,完结篇)
今天早上匆匆写了篇文章,上班时间也找了找资料,也没找到任何有效性的说明,所以还得自己来。
还是先上一段测试代码:
{
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:
{
// 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 指令类似于 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指令。
在这里再补充一点吧。免得大家看了没什么收获,其实方法都告诉大家了,可以自己去尝试啊,自己动手才能记得牢啊。
代码改为:
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:
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#>>里看到这方面的内容,原意大概是:
这样看来其实可以理解:leave指令会执行其后有层次关系finally,然后跳至函数的末尾,这点MSDN说的很含糊。