委託是一種定義方法簽名的類型。 當執行個體化委託時,您可以將其執行個體與任何具有相容簽名的方法相關聯。 您可以通過委託執行個體調用方法。
在表面上,委託很簡單,使用new來構造委託執行個體。使用委託執行個體的變數名來調用回呼函數。實際情況是編譯器,CLR在幕後做了大量的工作來隱藏其複雜性,只有瞭解了這些幕後的東西,你才能真正的掌握它、靈活的運用它。
1、聲明委託
namespace DelegateDemo{ internal delegate void HelloCallBack(string name); class Program { static void Main(string[] args) { } }}
通過ildasm查看中間代碼,如下
編譯器自動產生一個helloCallBack的類,類裡面有構造方法,回調方法Invoke,非同步回調方法(BeginInvoke,EndInvoke),
它繼承MulticastDelegate類,MulticastDelegate繼承Delegate類,c#有兩個委託類(Delegate,MulticastDelegate)是有曆史原因的,原來是要合并成一個類,但快到發布時間了,合并它需要重新測試,所以Delegate就倖存下來)。
2、委託的執行個體化
internal delegate void HelloCallBack(string name); class Program { static void Main(string[] args) { HelloCallBack helloShow = new HelloCallBack(ShowName); Console.ReadLine(); } static void ShowName(string name) { Console.WriteLine(name); } }
中間代碼如下
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint / //第一個被執行的方法被稱為入口函數
// Code size 15 (0xf)
.maxstack 3 //定義函數代碼所用堆棧的最大深度
.locals init ([0] class DelegateDemo.HelloCallBack helloShow) //分配一個局部變數helloShow
IL_0000: nop //如果修補作業碼,則填充空間,未執行任何有意義的操作
IL_0001: ldnull //將Null 參考推送到計算堆棧上
IL_0002: ldftn void DelegateDemo.Program::ShowName(string) //將ShowName的函數指標(非託管指標 native int 類型)推送到計算堆棧上
IL_0008: newobj instance void DelegateDemo.HelloCallBack::.ctor(object, native int) //建立一個新對象,並將對象引用推送到計算堆棧上
IL_000d: stloc.0 //從計算堆棧的頂部彈出當前值並將其儲存到索引 0 處的局部變數helloShow中
IL_000e: ret //從當前方法返回,並將傳回值(如果存在)從調用方的計算堆棧推送到被呼叫者的計算堆棧上
} // end of method Program::Main
從中間代碼我們可以看到,HelloCallBack類建構函式有兩個參數,HelloCallBack::.ctor(object, native int),而My Code是
HelloCallBack helloShow = new HelloCallBack(ShowName);只有一個參數,應該編譯不過。編譯器在這個地方幫我們做了一些東西,
當它知道要構造的是委託時,就會分析原始碼來確定引用的是哪個對象,那個方法。對象引用傳遞給object,ShowName的函數指標傳遞給native int
3、調用回調方法
原始碼如下
internal delegate void HelloCallBack(string name); class Program { static void Main(string[] args) { HelloCallBack helloShow = new HelloCallBack(ShowName); helloShow("hello"); } static void ShowName(string name) { Console.WriteLine(name); Console.ReadLine(); } }
中間代碼如下
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 27 (0x1b)
.maxstack 3
.locals init ([0] class DelegateDemo.HelloCallBack helloShow)
IL_0000: nop
IL_0001: ldnull
IL_0002: ldftn void DelegateDemo.Program::ShowName(string)
IL_0008: newobj instance void DelegateDemo.HelloCallBack::.ctor(object,
native int)
IL_000d: stloc.0
IL_000e: ldloc.0 //將索引 0 處的局部變數載入到計算堆棧上
IL_000f: ldstr "hello" //把一個字串常量裝入堆棧
IL_0014: callvirt instance void DelegateDemo.HelloCallBack::Invoke(string) //對對象調用後期Binder 方法,並且將傳回值推送到計算堆棧上
IL_0019: nop
IL_001a: ret
} // end of method Program::Mains
helloShow("hello") 等價於 helloShow.Invoke("hello");
完整的代碼如下
internal delegate void HelloCallBack(string name); class Program { static void Main(string[] args) { HelloCallBack helloShow = new HelloCallBack(ShowName); helloShow.Invoke("hello");
Console.ReadLine();
} static void ShowName(string name) { Console.WriteLine(name); } }
Invoke是怎麼實現的呢,查看中間代碼如下
.method public hidebysig newslot virtual instance void Invoke(string name) runtime managed{} // end of method HelloCallBack::Invoke
runtime managed 表示此方法運行時有CLR處理,我推測類似於
Delegate[] delegates = helloShow.GetInvocationList();
for (int i = 0; i < delegates.Length; i++)
{
Delegate callback = delegates[i];
MethodInfo method = callback.Method;
method.Invoke(helloShow.Target, new object[] { "hello" });
}
4、委託鏈
委託鏈是委派物件的集合,利用它,可以調用委託的所有方法
Delegate有兩個公用屬性
Target 擷取類執行個體,當前委託將對其調用執行個體方法。(靜態方法訪問空)
Method 擷取委託所表示的方法。
多播委託的使用如下
namespace DelegateDemo{ internal delegate void HelloCallBack(string name); class Program { static void Main(string[] args) { HelloCallBack helloShow = new HelloCallBack(ShowName); helloShow += ShowCHName; helloShow.Invoke("hello"); Console.ReadLine(); } public static void ShowCHName(string name) { Console.WriteLine("你好:"+name); } public static void ShowName(string name) { Console.WriteLine(name); } }}
查看MulticastDelegate源碼可知,委託鏈儲存在private object _invocationList;
HelloCallBack helloShow = new HelloCallBack(ShowName);
_invocationList被初始化成object[],數組的第一個元素為new HelloCallBack(ShowName)委託
helloShow += ShowCHName;
+使用了運算子多載,它實際調用的是Delegate的Combine,-號實際調用的是Delegate的Remove