.Net委託型別解析

發表於2016-06-18

不像Windows API中使用C語言風格的函式指標這種不安全的方式進行回撥。.Net中此功能使用使用更為安全和麵向物件的委託(delegate)來完成。委託是一個型別安全的物件,它指向程式中另一個以後會被呼叫的方法(或多個方法)。

委託型別包含3個重要資訊:

  • 它所呼叫的方法的名稱
  • 該方法的引數(可選)
  • 該方法的返回值(可選)

當上述資訊被提供後,委託可以在執行時動態呼叫其指向的方法。很重要的一點:.Net中每個委託都被自動賦予同步或非同步訪問方法的能力。

定義委託

在C#中使用delegate關鍵字建立一個委託。我們稱這種類為委託類。委託類的例項成為委託物件。從概念上說,委託物件是一種指向一個或多個方法(靜態或非靜態)的引用。要求是此委託匹配它指向的方法的簽名。

如下委託可以指向一任何傳入兩個整數返回一個整數的方法。

  • 定義委託後,系統生成一個派生自MulticastDelegate類的密封類。此類中有3個方法:
  • Invoke()方法,用來以同步方式呼叫委託維護的每個方法。(不能在C#中顯示呼叫此方法,Invoke()在後臺被呼叫)
  • BeginInvoke()與EndInvoke()方法在第二個執行緒上非同步呼叫當前方法。

開發人員建立第二個執行執行緒的原因呼叫比較耗時的方法。(相當於委託順帶實現了一些System.Threading名稱空間管理的執行緒問題)

這個委託的密封類大概如下:

下面給一個簡單的委託示例:

委託型別安全的體現

如果傳入一個與委託宣告不匹配的方法,將在編譯時報錯。如上例中如果傳入int SquareNumber(int),將會導致一個編譯時錯誤。

獲取委託中呼叫函式列表的方法,示例:

假設有名為delObj的委託物件,使用如下方式得到呼叫函式的資訊

Method屬性表示呼叫的函式的簽名,Target表示呼叫的函式所在的物件的型別名,所以如果委託呼叫的是一個靜態方法則Target不會有任何顯示,只有當委託呼叫的是一個例項方法時,Target屬性才有值。

更完整的委託應用(示例來自C#與.Net3.0高階程式設計),程式碼:

汽車類:

主函式:

對多路廣播的支援

.Net委託內建多路,即一個委託可以維護一個可呼叫方法的列表而不只是單獨一個方法,使用過載過的+=運算子可以向一個委託物件新增多個方法。關於對對路廣播的支援可以參考上述示例。

在多路廣播的支援中有一個需要注意的問題,一個委託呼叫的多個方法需要無引數且無返回值,因為在呼叫委託時,即使傳入了引數也不知道具體應該傳給哪一個方法,即使這些方法有返回值也不知道該接受那個函式的返回值。所以說直接不要呼叫有引數及返回值的方法,這點與事件關聯多個事件處理方法時對處理方法簽名的要求相同(可以參見本系列介紹事件的文章)。

注意:我們可以用呼叫方法的語法”呼叫”委託物件。這樣會呼叫委託物件所引用的方法。(事件的觸發與委託的呼叫相同,本來事件就是一個委託型別的物件)。這些方法的呼叫是在呼叫委託的方法所在的執行緒中完成的。這種呼叫稱同步呼叫。

C#2.0編譯器的委託類推測功能

C#編譯器引入了在建立委託變數時可以推測其型別的能力。這樣就可以將一個方法賦給隱式建立的委託物件。

示例:

委託協變(covariance)

允許建立一個委託,其返回的物件的型別是繼承關係的,示例程式碼:

委託逆變,其中引數具有整合關係,委託簽名的引數型別(派生型別)比方法具有的引數型別(基型別)更具體。定義一個引數型別是派生型別的委託,這個委託可以接收具有基型別引數的方法,因為派生型別隱式轉換成了基型別。注意此方法必須接收與委託簽名相同的引數型別(派生型別),雖然方法的簽名中引數是基型別。

接下來說一下委託在多執行緒程式中的應用,主角有兩個:ThreadStart和ParameterizedThreadStart,它們都定義與System.Threading名稱空間下。

使用這兩個委託,你可以以程式設計方式建立此執行緒來分擔一些任務,步驟如下:

  1. 建立一個方法作為新執行緒的入口點。
  2. 建立一個ParameterizedThreadStart(或ThreadStart)委託,並把之前所定義的方法傳給委託的建構函式。
  3. 建立一個Thread物件,並把ParameterizedThreadStart或ThreadStart委託作為建構函式的引數。
  4. 建立任意初始化執行緒的特性(名稱、優先順序等)。
  5. 呼叫Thread.Start()方法。

完成上述步驟,在第2步建立的委託所指向的方法將線上程中儘快開始執行。

ThreadStart委託指向一個沒有引數、無返回值的方法,它在呼叫一個被設計用來僅僅在後臺執行、而沒有更多的互動時非常有用。它的侷限在於無法給這個函式出入引數,所以在.Net2.0中出現了ParameterizedThreadStart了方法,它可以接受一個包含了任意個數的引數(傳給它要呼叫的方法的)的Object型別物件做引數(即允許使用者為新執行緒要執行的方法傳入一個物件作為引數)。但注意這兩種委託指向的函式的返回值都必須是void。

看看示例程式碼,首先是ThreadStart委託的:

接下來的程式碼示例了ParameterizedThreadStart委託的使用:

詳細通過上面兩段簡單的程式碼示例,你已經對ThreadStart和ParameterizedThreadStart的使用有了全面的瞭解。

另外有一點需要說的,有些情況下可以省略這個委託物件的構造,即構造Thread物件時,直接向Thread的建構函式傳入一個方法的名稱,而不用先構造一個委託的物件。傳入的方法既可以是靜態方法也可以是例項方法。

    另外委託在非同步程式設計中的作用見非同步程式設計的文章

參考資料:

C#與.Net3.0高階程式設計

C#與.Net2.0實戰

CLR via C# 第二版

相關文章