委托主要是為了實 現回調函數機制,可以理解為函數指針(唯一不同的在於多了委托鏈這個概念)。
然而用的時候可以這麼理解,但是委托的內部機制是比較復雜的。
一個委托的故事
delegate void razor(String userName);
一個簡單的委托被定義了。
實際上在編譯後這段代碼就和下面的代碼很像了:
class Razor : System.MulticastDelegate { //構造函數 public Razor(Object @object,IntPtr method); public virtual void Invoke(Int32 value); //實現對回調方法的異步回調 public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object @object); public virtual void EndInvoke(IAsyncResult result); }
也就是說委托實際上是一個派生自MulticastDelegate的類,其中有四個方法。
而由於繼承MulticastDelegate類,所以還繼承了MulticastDelegate類的字段、方法和屬性。
有三個字段最重要,分別是指向委托傳進的實例方法的操作對象_target(如果是靜態方法就是NULL),CLR用來標識回調方法的_methodPtr(這裡不是指針,而是一個內部的參數值,用以表示某方法),構造委托鏈時委托數組的引用_invokationList(在委托通過Delegate.Combine或者+=綁定了多個回調函數後才有值,否則為null).
而在使用委托的時候會:
先去看此委托的_invokationList是否為null,為null就說明沒有建立委托鏈,那麼實際上就用了委托Razor(實際上是一個類的實例),調用Invoke方法,此時會傳給Invoke方法一個參數值,它就是_methodPtr存放的內部的參數值,用於標識傳進來的實例方法的參數值。
如果_invokationList不為null說明已經建立了委托鏈,那麼久分別去調用委托數組裡面各個委托的Invoke方法,執行的方法自然是各個委托裡面_methodPtr指定的方法。
而至於移除委托(用Delegate.Remove或者-=)。
執行委托鏈中的函數是按照綁定的順序開始執行,但是如果有返回,那麼返回的一定是最後一個綁定的函數的返回結果。
然而這種執行委托鏈的方式有一些問題,比如某個委托鏈中的委托拋了異常或者因為調用數據庫查詢的阻塞時間太長,導致後面的委托函數都無法執行。
所以MulticastDelegate類提供了一個實例方法GetInvokationList(),他會返回_invokationList指向的Delegate數組。
泛型委托
泛型委托是為了解決相似的委托過多的問題,而且.NET提供的泛型委托也很簡單,17個參數不同的Action委托和17個參數不同的Func委托.
其中Action委托都是返回值為void,而Func委托的返回值類型為自己指定的類型的TResult。
然而如果需要使用ref和out關鍵字以傳引用的方式傳遞參數,那麼就不得不定義自己的委托。因為泛型委托的協變和逆變的時候會用到in和out標識,為了搞混就不能這麼用。(此處可參考泛型那一章)
C#關於委托的語法糖
一個正常的委托是下面這樣的
static void Main(string[] args) { Razor razor=null; razor += new Razor(Blower); razor("Troy123"); Console.Read(); } static void Blower(String userName) { Console.WriteLine(userName + ":實際上這是一個吹風機"); }
然而
razor += new Razor(Blower);
這樣的語法看起來很奇怪(一開始奇怪,其實還好啦),所以C#提供個一些簡化的語法:
一些函數會像下面那樣可以直接傳入回調方法,而不是像之前一樣需要new Razor這樣去調用構造委托對象。表面上是如此,實際上只是在內部做了處理,還是會去構造委托對象然後調用。
void 回調函數(object obj) { Console.WriteLine(obj); } ThreadPool.QueueUserWorkItem(回調方法, 5);
ThreadPool.QueueUserWorkItem(l=>Console.WriteLine(o),5);
delegate void Bar(out Int32 z); Func<string> f = () => "Troy"; Func<Int32,string> f1 = (Int32 l) => l.ToString(); Func<Int32,string> f2 = (l) => l.ToString();//推斷類型 Func<Int32, Int32, string> f3 = (l, m) => { Console.WriteLine(m); return l.ToString(); };//多條語句加大括號 Bar b = (out Int32 n) => n = 5;//對於引用類型,必須顯示指定
委托和反射
System.Delegate.MethodInfo提供了一個CreateDelegate方法,顧名思義,就是去動態創建一個委托。
然後可以用Delegate.DynamicInvoke方法去動態調用它。
關於這個具體的也沒有想到太多的地方會用到,畢竟見的世面少了。