1 finally與return
try-catch-finally是很經常使用的語法結構,用來控制可能發生異常時的程序流程,其中catch和finally至少要有一個。初學try語法時可能會要問一個問題:若是在try塊中return,那麼finally還會執行嗎?答案是確定的。這個很是容易驗證,就不舉例子了。這樣帶來一些很好的特性,例如咱們能夠在try塊中嘗試打開數據庫,而後讀取數據,而後直接把獲得的數據return出去,關閉數據鏈接的工做就交給finally來作——finally中先判斷數據庫是否正常打開了,打開了就關閉。這樣代碼寫起來很清晰,每一個部分各作各的事。這樣咱們也能夠很是確定的說,不管發生什麼狀況(只要不是進程被強行殺掉),finally中的內容必定是要執行的。
那麼是否是能夠再問一個問題——若是在finally塊中也寫了return,那麼會怎麼樣呢?試驗一下就很容易知道,finally塊中是不容許寫return的,若是必定要寫,就會獲得一個編譯期錯誤:
error CS0157: Control cannot leave the body of a finally clause
2 先return?先finally?
既然finally必定是要執行的,即便try塊中有return,那麼這二者的執行順即是怎麼樣的呢?簡單的作一個實驗(下面要說明,這個實驗看上去的結果並不這麼直觀的表現出它的內在):html
using System;
public class TestClass1
...{
public static void Main()
...{
Console.WriteLine("{0}", Func1());
}
public static int Func1()
...{
int a = 1;
try
...{
return a;
}
finally
...{
a++;
}
}
}
運行這個程序,很容易獲得結果爲「1」。那麼看上去是執行return在先,而finally在後了。真的是這樣嗎?
例子中我要return的a是一個值類型,那麼若是是引用類型,結果又會如何呢?web
using System;
public class TestClass2
...{
public int value = 1;
}
public class TestClass1
...{
public static void Main()
...{
Console.WriteLine("{0}", Func2().value);
}
public static TestClass2 Func2()
...{
TestClass2 t = new TestClass2();
try
...{
return t;
}
finally
...{
t.value++;
}
}
}
這一次運行的結果並非1,而是2。顯然,運行Func2()返回的結果並不直接是return後面寫的t,而是通過finally塊執行後值發生變化的t。如何來解釋這種區別呢?
3 CLR的棧
要解釋這種區別,就須要看看其IL是什麼,從調用函數、參數棧的角度來理解。CLR在執行中也有棧,但這個棧的用途與傳統的本地代碼中的棧並不徹底相同。本地代碼中棧的用處很是大,不但能夠用來臨時保存寄存器的值,還用來保存局部變量,此外還用來保存部分或所有傳給函數的參數,而函數的返回值通常是經過EAX寄存器來傳遞的,而不是用棧。但在CLR中,局部變量並不是顯式的用棧來保存,棧只是用來調用函數時傳遞參數,此外,函數的返回值也是用棧來保存的。當調用一個函數時,將函數所須要的參數依次壓棧,函數裏面直接取用這些參數,在函數返回時將返回值壓棧,函數返回後,棧頂便是返回值。若是調用者並不關心返回值,那麼須要執行一下pop語句,把返回值彈出,這樣保證函數在調用先後棧頂的位置是相同的。
當經過壓棧傳遞參數時,參數的類型不一樣,壓棧的內容也不一樣。若是是值類型,壓棧的就是通過複製的參數值,若是是引用類型,那麼進棧的只是一個引用,這也就是咱們所熟悉的,傳遞值類型時,函數內修改參數值不會影響函數外,而引用類型的話則會影響。數據庫
代碼中當咱們執行new時,對應的IL是newobj,其結果是建立一個TestClass2類型的對像並返回一個引用放置於棧上,以後的stloc就將這個引用保存爲局部變量,因而棧上沒有了其餘內容。Try塊並無執行太多操做,只是把剛保存的引用再放到棧上,再保存爲另外一個局部變量,這個局部變量就是稍後要返回的引用,此時咱們擁有兩個局部變量,但它們是指向同一個對象的兩個引用。Finally塊先拿出開始時保存的引用放到棧上,dup語句使得棧頂再增長一個徹底同樣的引用,以後ldfld語句是從棧頂對象取一個成員放到棧上,所取的成員是value,以後再往棧上壓一個1,再執行add,就實現了1+1=2的過程,add從棧上彈出兩個值,再向棧壓回一個值。此時再調用stfld就把剛剛壓棧的2設置給棧上2之下的那個引用所指對象的value屬性上。而在finally以後的部分纔是真正的return,它試圖取出咱們所保存的第二個局部變量壓棧,將它做爲返回值。但對於引用類型來講,它與先前所操做的引用所指的是同一對象,所以finally塊中的操做會影響到返回值,也就很是好理解了。
4 改編
知道了finally與return的實現原理,也就不難作出進一步的推廣。例如把程序改爲這樣(返回時由直接返回t變爲在t上調用一個作一些操做後返回本身的函數),其執行結果也不難猜出來吧:asp.net
using System;
public class TestClass2
...{
public int value = 1;
public TestClass2 Double()
...{
value *= 2;
return this;
}
}
public class TestClass1
...{
public static void Main()
...{
Console.WriteLine("{0}", Func2().value);
}
public static TestClass2 Func2()
...{
TestClass2 t = new TestClass2();
try
...{
return t.Double();
}
finally
...{
t.value++;
}
}
}
文章出處:http://www.diybl.com/course/4_webprogram/asp.net/asp_netshl/2008318/105033.html函數