Try..Finally..相信自己的眼睛
问题提出
1 try 2 { 3 return x; 4 } 5 finally 6 { 7 x = null; 8 }
上面这段代码到底怎么执行的?
try..catch..finally 介绍
在MSDN中,try..catch..finally 的介绍如下:
- finally 块用于清除 try 块中分配的任何资源,以及运行任何即使在发生异常时也必须执行的代码。 控制总是传递给 finally 块,与 try 块的退出方式无关。
- catch 用于处理语句块中出现的异常,而 finally 用于保证代码语句块的执行,与前面的 try 块的退出方式无关。
- catch 和 finally 一起使用的常见方式是:在 try 块中获取并使用资源,在 catch 块中处理异常情况,并在 finally 块中释放资源。
典型用法:
1 void ReadFile(int index) 2 { 3 string path = @"c:\users\public\test.txt"; 4 char[] buffer = new char[10]; 5 6 StreamReader file = new StreamReader(path); 7 try 8 { 9 file.ReadBlock(buffer, index, buffer.Length); 10 } 11 catch (IOException e) 12 { 13 Console.WriteLine("Error reading from {0}. Message = {1}", path, e.Message); 14 } 15 finally 16 { 17 if (file != null) 18 { 19 file.Close(); 20 } 21 } 22 }
通常 finally 中的代码只负责清理资源。
那么,如果 finally 中包含业务逻辑,try..finally..的执行顺序会对业务逻辑有怎样的影响呢?
try..finally 的执行顺序
回到问题,
void Main() { Console.WriteLine(TestTryFinally()); } public string TestTryFinally() { string x = "init"; try { x = "try"; return x; } finally { x = "finally"; } }
这里的执行顺序是:
- 执行 return 之前的代码
- 对 return 语句求值
- 执行 finally 中的代码
- 在第 2 步中的求值结果被返回
所以,具体是否对返回值有影响,得看 x 变量的类型。如果是不可变类型,则 finally 中的代码对 return 的求值结果没有任何影响。而如果是可变类型,则 finally 中的代码会改变 return 求值结果的内容。
上述代码,在 x 类型为 string 时,返回值为 "try"。
查看 IL 代码,
1 IL_0000: ldarg.0 2 IL_0001: call UserQuery.TestTryFinally 3 IL_0006: call System.Console.WriteLine 4 5 TestTryFinally: 6 IL_0000: ldstr "init" 7 IL_0005: stloc.0 // x 8 IL_0006: ldstr "try" 9 IL_000B: stloc.0 // x 10 IL_000C: ldloc.0 // x 11 IL_000D: stloc.1 // CS$1$0000 12 IL_000E: leave.s IL_0017 13 IL_0010: ldstr "finally" 14 IL_0015: stloc.0 // x 15 IL_0016: endfinally 16 IL_0017: ldloc.1 // CS$1$0000 17 IL_0018: ret
发现在 stloc.1 处会创建 CS$1$0000 临时变量来存储 return 返回值。
从程序集反编译代码查看结果,程序已经被优化。
1 // ConsoleApplication11_TryFinallyTest.Program 2 public string TestTryFinally() 3 { 4 string result; 5 try 6 { 7 string x = "try"; 8 result = x; 9 } 10 finally 11 { 12 } 13 return result; 14 }
更多测试结果
1 using System; 2 using System.Text; 3 4 namespace ConsoleApplication11_TryFinallyTest 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 Console.WriteLine(MethodA()); 11 Console.WriteLine(a); 12 13 Console.WriteLine(MethodB().ToString()); 14 Console.WriteLine(b.ToString()); 15 16 Console.WriteLine(MethodC()); 17 Console.WriteLine(c); 18 19 Console.WriteLine(MethodD().ToString()); 20 Console.WriteLine(d.ToString()); 21 22 Console.WriteLine(MethodE().ToString()); 23 Console.WriteLine(e.ToString()); 24 25 Console.ReadKey(); 26 } 27 28 static string a; 29 static string MethodA() 30 { 31 try 32 { 33 a = "tryA"; 34 return a; 35 } 36 finally 37 { 38 a = "finallyA"; 39 } 40 } 41 42 static StringBuilder b = new StringBuilder(); 43 static StringBuilder MethodB() 44 { 45 try 46 { 47 b.Append("tryB"); 48 return b; 49 } 50 finally 51 { 52 b.Append("finallyB"); 53 } 54 } 55 56 static int c; 57 static int MethodC() 58 { 59 try 60 { 61 c = 3; 62 return c; 63 } 64 finally 65 { 66 c = 4; 67 } 68 } 69 70 static Person d; 71 static Person MethodD() 72 { 73 try 74 { 75 d = new Person() { Name = "tryD" }; 76 return d; 77 } 78 finally 79 { 80 d = new Person() { Name = "finallyD" }; 81 } 82 } 83 84 static Person e; 85 static Person MethodE() 86 { 87 e = new Person() { Name = "E" }; 88 try 89 { 90 e.Name = "tryE"; 91 return e; 92 } 93 finally 94 { 95 e.Name = "finallyE"; 96 } 97 } 98 99 class Person 100 { 101 public string Name { get; set; } 102 public override string ToString() 103 { 104 return Name; 105 } 106 } 107 } 108 }
参考资料
- try-catch-finally(C# 参考)
- What really happens in a try { return x; } finally { x = null; } statement?