建議61:避免在finally內撰寫無效代碼
在闡述建議之前,需要先提出一個問題:是否存在一種打破try-finally執行順序的情況,答案是:不存在(除非應用程序本身因為某些很少出現的特殊情況在try塊中退出)。應該始終認為finally內的代碼會在方法return之前執行,哪怕return在try塊中。
正是這點,可能會讓你寫出無效的代碼,有時候,這樣的無效代碼會是一個隱藏很深的Bug。
看下面代碼:
private static int TestIntReturnBelowFinally()
{
int i;
try
{
i = 1;
}
finally
{
i = 2;
Console.WriteLine("\t將int結果改為2,finally執行完畢");
}
return i;
}
返回值是2。
但是:
private static int TestIntReturnInTry()
{
int i;
try
{
return i = 1;
}
finally
{
i = 2;
Console.WriteLine("\t將int結果改為2,finally執行完畢");
}
}
返回值是1。
再看下面代碼:
static User TestUserReturnInTry()
{
User user = new User() { Name = "Mike", BirthDay = new DateTime(2010, 1, 1) };
try
{
return user;
}
finally
{
user.Name = "Rose";
user.BirthDay = new DateTime(2010, 2, 2);
Console.WriteLine("\t將user.Name改為Rose");
}
}
user類:

TestUserReturnInTry方法返回的User中,Name的值已經改為Rose了。
現在來解釋為什麼上面3個函數會有3種結果。查看TestIntReturnBelowFinally的finally部分的IL代碼:
finally
{
IL_0004: ldc.i4.2
IL_0005: stloc.0
IL_0006: ldstr bytearray (09 00 06 5C 69 00 6E 00 74 00 D3 7E 9C 67 39 65 // ...\i.n.t..~.g9e
3A 4E 32 00 0C FF 66 00 69 00 6E 00 61 00 6C 00 // :N2...f.i.n.a.l.
6C 00 79 00 67 62 4C 88 8C 5B D5 6B ) // l.y.gbL..[.k
IL_000b: call void [mscorlib]System.Console::WriteLine(string)
IL_0010: endfinally
} // end handler
IL_0011: ldloc.0
IL_0012: ret
}
“IL_0004: ldc.i4.2”首先將2壓入棧頂
“IL_0005: stloc.0”將最頂層堆棧的值,也就是2賦值給本地變量,也就是 i (index 0)
“IL_0011: ldloc.0”將本地變量 i (index 0)的值再次壓入棧
“IL_0012: ret”結束函數,同時把棧內的返回值壓入調用者的棧中。就函數將2賦值給了返回值。
看方法TestIntReturnInTry()的Debug版本的IL代碼:
.method private hidebysig static int32 TestIntReturnInTry() cil managed
{
// 代碼大小 27 (0x1b)
.maxstack 2
.locals init ([0] int32 i,
[1] int32 CS$1$0000)
IL_0000: nop
.try
{
IL_0001: nop
IL_0002: ldc.i4.1
IL_0003: dup
IL_0004: stloc.0
IL_0005: stloc.1
IL_0006: leave.s IL_0018
} // end .try
finally
{
IL_0008: nop
IL_0009: ldc.i4.2
IL_000a: stloc.0
IL_000b: ldstr bytearray (09 00 06 5C 69 00 6E 00 74 00 D3 7E 9C 67 39 65 // ...\i.n.t..~.g9e
3A 4E 32 00 0C FF 66 00 69 00 6E 00 61 00 6C 00 // :N2...f.i.n.a.l.
6C 00 79 00 67 62 4C 88 8C 5B D5 6B ) // l.y.gbL..[.k
IL_0010: call void [mscorlib]System.Console::WriteLine(string)
IL_0015: nop
IL_0016: nop
IL_0017: endfinally
} // end handler
IL_0018: nop
IL_0019: ldloc.1
IL_001a: ret
} // end of method Program::TestIntReturnInTry
TestIntReturnInTry在IL中創建了兩個本地變量 i 和CS$1$0000 ,i 存儲的是1,然後finally中 i 被賦值為2。調用者真正得到的是由IL創建的CS$1$0000所對應的值。用Reflector查看C#代碼:
private static int TestIntReturnInTry()
{
int i;
int CS$1$0000;
try
{
CS$1$0000 = i = 1;
}
finally
{
i = 2;
Console.WriteLine("\t將int結果改為2,finally執行完畢");
}
return CS$1$0000;
}
實際上,finally中i=2沒有任何意義,所以在本函數的release版本中,IL中找不到對應的代碼:
.method private hidebysig static int32 TestIntReturnInTry() cil managed
{
// 代碼大小 17 (0x11)
.maxstack 1
.locals init ([0] int32 CS$1$0000)
.try
{
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: leave.s IL_000f
} // end .try
finally
{
IL_0004: ldstr bytearray (09 00 06 5C 69 00 6E 00 74 00 D3 7E 9C 67 39 65 // ...\i.n.t..~.g9e
3A 4E 32 00 0C FF 66 00 69 00 6E 00 61 00 6C 00 // :N2...f.i.n.a.l.
6C 00 79 00 67 62 4C 88 8C 5B D5 6B ) // l.y.gbL..[.k
IL_0009: call void [mscorlib]System.Console::WriteLine(string)
IL_000e: endfinally
} // end handler
IL_000f: ldloc.0
IL_0010: ret
} // end of method Program::TestIntReturnInTry
用Reflector查看release版本中C#代碼:
private static int TestIntReturnInTry()
{
int CS$1$0000;
try
{
CS$1$0000 = 1;
}
finally
{
Console.WriteLine("\t將int結果改為2,finally執行完畢");
}
return CS$1$0000;
}
再解釋第三個方法TestUserReturnInTry為什麼返回的是“Rose”。Reflector查看release版本中C#代碼:
private static User TestUserReturnInTry()
{
User CS$1$0000;
User <>g__initLocal0 = new User {
Name = "Mike",
BirthDay = new DateTime(0x7da, 1, 1)
};
User user = <>g__initLocal0;
try
{
CS$1$0000 = user;
}
finally
{
user.Name = "Rose";
user.BirthDay = new DateTime(0x7da, 2, 2);
Console.WriteLine("\t將user.Name改為Rose");
}
return CS$1$0000;
}
User是引用類型, CS$1$0000 = user;說明CS$1$0000和user指向的是同一個對象,當在finally中 user.Name = "Rose"時CS$1$0000的Name也會變為“Rose”。所以返回的CS$1$0000的Name為“Rose”。
再舉一個例子:
private static User TestUserReturnInTry2()
{
User user = new User() { Name = "Mike", BirthDay = new DateTime(2010, 1, 1) };
try
{
return user;
}
finally
{
user.Name = "Rose";
user.BirthDay = new DateTime(2010, 2, 2);
user = null;
Console.WriteLine("\t將user置為anull");
}
}
返回的結果不是null,而一個Name=“Rose”,BirthDay = new DateTime(2010, 2, 2)的User對象。Reflector查看release版本中C#代碼:
private static User TestUserReturnInTry2()
{
User CS$1$0000;
User <>g__initLocal1 = new User {
Name = "Mike",
BirthDay = new DateTime(0x7da, 1, 1)
};
User user = <>g__initLocal1;
try
{
CS$1$0000 = user;
}
finally
{
user.Name = "Rose";
user.BirthDay = new DateTime(0x7da, 2, 2);
user = null;
Console.WriteLine("\t將user置為anull");
}
return CS$1$0000;
}
CS$1$0000和user指向的是同一個對象,當在finally中 user=null 時,只是user指向為null了,CS$1$0000指向的對象並沒有變。
轉自:《編寫高質量代碼改善C#程序的157個建議》陸敏技