程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> try中的return與finally執行的順序

try中的return與finally執行的順序

編輯:C#入門知識

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,那麼這兩者的執行順便是怎麼樣的呢?簡單的做一個實驗(下面要說明,這個實驗看上去的結果並不這麼直觀的表現出它的內在):


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是一個值類型,那麼如果是引用類型,結果又會如何呢?


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上調用一個做一些操作後返回自己的函數),其執行結果也不難猜出來吧:

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
              ...{
                &n

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved