一、委託
設想,如果我們寫了一個廚師做菜方法用來做菜,裡面有 拿菜、切菜、配菜、炒菜 四個環節,但編寫此方法代碼的人想讓 配菜 這個環節讓調用方法的人實現,換句話說,就是想在方法被調用時接收代碼 作為參數,在方法中執行這端傳進來的代碼。
但,怎麼為一個方法傳 代碼 進來呢。當然大家想到了傳遞介面方式來實現,咱先不討論介面,因為微軟為我們提供了一個叫做 【委託】 的類型。
(一)、委託基礎:
1. 先看看代碼:
(1).定一個方法:void SayHi(string name){Console.WriteLine(“Hi~”+name+”! ” );}
(2).聲明一種委託類型:delegate void DGSayHi(string uName);
(3).建立委託類型對象:DGSayHi dgObj = new DGSayHi(SayHi);//建構函式中傳入了方法
(4).執行委託:
dgObj(“JamesZou”); //調用委託(奇怪:對象加括弧 的方式調用。後面解釋。)
輸出:Hi~JamesZou!
2. 什麼是委託。
(1)概念:“C# 中的委託類似於 C 或 C++ 中的函數指標。使用委託使程式員可以將方法引用封裝在委派物件內。然後調用該委派物件就可以執行委派物件內方法引用指向的方法,而不必在編譯時間知道將調用哪個方法(如參數為委託類型的方法,也就是提供了為程式回調指定方法的機制)。”-- 引自MSDN
(2)通俗:就是一個能存放很多方法的指標的調用清單(但方法簽名必須和委託類型簽名一樣),你一調用這個清單,那麼清單裡的所有的指標所對應的方法就會依次被執行。
(3)比方說:有三台機器A、C、D,點一個紅色按鈕就會運行。操作人員接到指令,要求在接到電話後分別開啟AD機器,然後然後工人就在接到電話後,先後開啟AD機器。(此例中的 三台機器就是方法,操作員,就可以看成是“委託”啦)
(4)概要圖例:
DGSayHi dgObj = new DGSayHi(SayHi);
dgObj(“James”); //調用委派物件,就會執行委派物件裡的方法。
3. 委託有什麼用。
A.能夠幫程式員在需要時,根據條件動態執行多個方法:(接上例代碼)
(1)定三個方法:
void SayHi(string name){Console.WriteLine(“Hi~”+name ); }
void DaZhaoHu(string name){ Console.WriteLine(“你好啊~”+name ); }
string OHaUo(string name){ Console.WriteLine(“OHaUo ~”+name ); return “JapHi”;}
(2)建立委託類型對象,並通過建構函式傳參方式向委派物件“註冊”第一個方法:
DGSayHi dgObj = new DGSayHi(SayHi);
(3)繼續“註冊兩個方法”:
dgObj+=DaZhaoHu;// (奇怪:對象之間用+=符號來操作。後面解釋)
//dgObj+=OhaUo;//注釋此行代碼,因為編譯時間報錯,OhaUo方法簽名與委託類型的簽名不一致(委託簽名無傳回值)。
(4)執行委派物件:
dgObj(“James”); //執行了此委託中註冊的兩個方法
輸出:
Hi~James
你好啊~James
(5)概要圖例
B. 委託作為方法參數(回調方法機制)
(1).接上例代碼,再定義一個方法:
void DoTestDelegateFun(DGSayHi dgObj){dgObj(“鋼鐵俠”);}
(2).調用此方法:
DoTestDelegateFun(SayHi);//輸出:Hi~鋼鐵俠(奇怪:竟然直接傳方法了。後面解釋)
C.委託文法糖
(1).注意到上面有3個地方我們都覺得“奇怪”:
a.調用委派物件dgObj(“JamesZou”);
b.向委託註冊方法 dgObj+=DaZhaoHu;
c.將方法作為參數 DoTestDelegateFun(SayHi);
這些用法其實都是FW為我們提供的簡便文法(它們有個可愛的名字:文法糖),在編譯時間由編譯器轉成完整的代碼:
a. dgObj.Invoke(“JamesZou”);
b. dgObj = (DGSayHi) Delegate.Combine(dgObj, new DGSayHi(this.DaZhaoHu));
//Combine方法將第二個參數,添加到dgObj中,並返回委派物件。
c. this.DoTestDelegateFun(new DGSayHi(this.SayHi));
Delegate類、Invoke方法、Combine方法是哪來的呢。
(二)、委託原理
1.delegate 關鍵字
(1).概念:delegate 關鍵字用於聲明一個參考型別,該參考型別可用於封裝命名方法或匿名方法。
(2)編譯後產生的的中間代碼。
請大家思考一下,關鍵字是類型嗎。不是。那編譯器遇到這個關鍵字做了什麼事情。藉助【IL反組譯工具】 我們來看一看:
a.開始-程式-如圖:
b.開啟專案檔夾下的bin\Debug檔案夾,找到程式集 CodeForFun.exe,拖入到【IL反組譯工具】介面中便可看到程式集的IL代碼:
找到我們定義了委託DGSayHi的類DelegateForFun,發現,裡面的 委託型別宣告 代碼
編譯前:delegate string DGSayHi(string uName);
變成了一個類:
單擊展開後我們再來看看:
看出什麼了。
(I).繼承了System.MulticastDelegate。
(II).包含了構造方法、BeginInvoke、EndInvoke、Invoke方法。
也就是說此時,delegate代碼已經編譯成了如下代碼:
編譯後:
class DGSayHi:System.MulticastDelegate
{
public DelegateForFun();
void Invoke(string value);
IAsyncResult BeginInvoke(string value,AsyncCallback callback,Object object);
void EndInvoke(IAsyncResult result);
}
(3)System.MulticastDelegate 類
下面我們來看看藉助.Net Reflector工具來查看類庫中的 MulticastDelegate 類
public abstract class MulticastDelegate : Delegate
由此我們可以看出繼承關係:DGSayHi –> MulticastDelegate–> Delegate
MulticastDelegate類中有3個重要的成員,其中兩個繼承自 Delegate :
a.三者的作用:
_methodPtr 裡儲存的就是 方法指標。
_target 裡用來儲存方法所在的對象。
_invocationList 其實使用時是個object數組,在註冊多個方法時,其他方法就儲存在此成員中,而它也就是 委託鏈 的關鍵容器。
b.概要圖:
圖中的委派物件 dgObj 在建立時建立了指向方法 SayHi的指標並儲存在 _methodPtr中;_target中儲存了SayHi方法所在的類的對象(比如我把這段代碼寫在表單裡按鈕的點擊方法中,那麼此時 _target就是 SayHi方法所在的表單對象);_invocationList 中儲存了追加的兩個方法的指標,但這兩個方法指標都是分別被裝在 MuticastDelegate對象中。
轉載請註明出處:ch01.深入理解C#委託及原理 開智網http://www.oumind.com