什么是委托? -> 初識(shí)委托
在很多應(yīng)用程序中(C,C++),需要對(duì)象使用某種回調(diào)機(jī)制,能夠與創(chuàng)建它 的實(shí)體進(jìn)行通信,在.NET平臺(tái)下,通過(guò)委托來(lái)提供了一種回調(diào)函數(shù)機(jī)制,在.NET平臺(tái)下,委托確?;卣{(diào)函數(shù)是類(lèi)型安全的(這也正是.NET FreamWork與非托管代碼的區(qū)別)。本質(zhì)上來(lái)講,委托是一個(gè)類(lèi)型安全的對(duì)象,它指向程序中另一個(gè)以后會(huì)被調(diào)用的方法(或多個(gè)方法),就是將方法作為 參數(shù)來(lái)傳遞.
C#中定義委托類(lèi)型
在C#中創(chuàng)建一個(gè)委托類(lèi)型時(shí),需要使用關(guān)鍵字 delegate 關(guān)鍵字,類(lèi)型名可以自定義,但是 委托要指定一個(gè)回調(diào)方法的簽名.
1 //聲明一個(gè)委托,該委托可以指向任何傳入兩個(gè)Int類(lèi)型并且方法的返回值為Int.2 public delegate int Binary(int x,int y);3 4 //聲明一個(gè)委托,該委托可以指向任何傳入一個(gè)String類(lèi)型并且方法返回值為Void5 public delegate void DelegateBackCall(string str);
使用委托發(fā)送對(duì)象狀態(tài)通知 -> 用委托回調(diào)靜態(tài)方法
先來(lái)看一段代碼:
void Main()
{
Program.Main();
}
public delegate void DelegateBackCall(int value);
class Program
{
public static void Main()
{
Counter(1,4,null);
Counter(1,4,new DelegateBackCall(StaticDelegateToConsole));
}
private static void Counter(int x,int y,DelegateBackCall foo) {
for(var i = x;i <= y;i++)
{
if(foo != null)
foo(i);21
}
}
private static void StaticDelegateToConsole(int num)
{
Console.WriteLine("Item : " + num);
}
}
復(fù)制代碼
首先 定義了一個(gè)名字為DelegateBackCall委托,該委托指定的方法要獲取Int類(lèi)型參數(shù),返回void,在Program類(lèi)中定義了私有的靜態(tài)方 法Counter,用來(lái)統(tǒng)計(jì)X到Y(jié)之間整數(shù)的個(gè)數(shù),同時(shí)呢,Counter方法還獲取一個(gè)Foo,Foo是對(duì)一個(gè)DelegateBackCall委托對(duì) 象的引用, 在方法體中 我們首先遍歷一下,如果Foo不為null,就調(diào)用Foo變量所指定的回調(diào)函數(shù).那么傳入的這個(gè)回調(diào)函數(shù)的是正在處理的那個(gè)數(shù)據(jù)項(xiàng)的值.
然后 在Program的Main函數(shù)中,第一次調(diào)用Counter時(shí),第三個(gè)參數(shù)傳遞的是Null,所在在Counter函數(shù)中是不會(huì)執(zhí)行回調(diào)函數(shù)的.
接著第二次調(diào)用Counter函數(shù)時(shí),給第三個(gè)參數(shù)傳遞一個(gè)新構(gòu)造的 DelegateBackCall委托對(duì)象(其實(shí)在此委托對(duì)象是方法的一個(gè)包裝器, 使方法能通過(guò)包裝器來(lái)間接的進(jìn)行回調(diào)),然后靜態(tài)方法StaticDelegateToConsole被傳給DelegateBackCll委托類(lèi)型的構(gòu) 造器(StaticDelegateToConsole就是要包裝的方法),當(dāng)Counter執(zhí)行時(shí),會(huì)在遍歷每個(gè)數(shù)據(jù)項(xiàng)之后調(diào)用靜態(tài)方法 StaticDelegateToConsole,最后輸出結(jié)果:
1 Result:2 Item : 1 3 Item : 24 Item : 3 5 Item : 4
使用委托發(fā)送對(duì)象狀態(tài)通知 -> 用委托調(diào)用實(shí)例方法
首先還是來(lái)看Main()函數(shù)中,第三個(gè)調(diào)用Counter的地方,在此將代碼貼出來(lái)吧.
復(fù)制代碼
1 class Program 2 { 3 public static void Main() 4 { 5 Counter(1,4,null); 6 Counter(1,4,new DelegateBackCall(StaticDelegateToConsole)); 7 8 Counter(1,4,new DelegateBackCall(new Program().InstanceDelegateToMessage)); 9 }10 private void InstanceDelegateToMessage(int num)11 {12 Console.WriteLine("Message : " + num);13 }14 }
復(fù)制代碼
在第三次調(diào)用Counter函數(shù)時(shí),第三個(gè)參數(shù)傳遞的是Program創(chuàng)建的實(shí)例 方法,這將委托包裝對(duì)InstanceDelegateToMessage方法的一個(gè)引用,該方法是一個(gè)實(shí)例方法.同調(diào)用靜態(tài)的一樣,當(dāng)Counter調(diào) 用Foo回調(diào)的時(shí)候會(huì)調(diào)用InstanceDelegateToMessage實(shí)例方法,新構(gòu)造的對(duì)象將作為隱式的this參數(shù)傳給這個(gè)實(shí)例方法.
最后的輸出結(jié)果為:
1 Message : 12 Message : 23 Message : 34 Message : 4
通過(guò)上面兩個(gè)例子我們知道委托可以包裝對(duì)實(shí)例方法和靜態(tài)方法的調(diào)用,如果是實(shí)例方法,那么委托需要知道方法操作的是哪個(gè)對(duì)象實(shí)例(包裝實(shí)例是很有用的,對(duì)象內(nèi)部的代碼可以訪問(wèn)對(duì)象的實(shí)例成員,這意味著對(duì)象可以維護(hù)一些狀態(tài),并在回調(diào)方法執(zhí)行期間利用這些狀態(tài)信心).
委托的協(xié)變和逆變
將一個(gè)方法綁定到委托時(shí),C#和CLR都允許引用類(lèi)型的協(xié)變和逆變
協(xié)變:方法的返回類(lèi)型是從委托的返回類(lèi)型派生的一個(gè)類(lèi)型
逆變:方法獲取的參數(shù)類(lèi)型是委托參數(shù)類(lèi)型的基類(lèi).
例如:
1 delegate object CallBack(FileStream file);2 3 string SomeMethod(Stream stream);
在上面代碼中,SomeMethod的返回類(lèi)型(string)派生自委托的返回類(lèi)型(object),這樣協(xié)變是可以的.
SomeMethod的參數(shù)類(lèi)型(Stream)是委托的參數(shù)類(lèi)型(FileStream)的基類(lèi),這樣逆變是可以的.
那么如果將String SomeMethod(Stream stream) 改為 Int SomeMethod(Stream stream);
那么C#編輯器會(huì)報(bào)錯(cuò).
說(shuō)明:協(xié)變性和逆變性只能用于引用類(lèi)型,不能用于值類(lèi)型和Void , 因?yàn)橹殿?lèi)型的存儲(chǔ)結(jié)構(gòu)是變化的,而引用類(lèi)型的存儲(chǔ)結(jié)構(gòu)始終是一個(gè)指針.
委托和接口的逆變和協(xié)變 -> 泛型類(lèi)型參數(shù)
委托的每個(gè)泛型類(lèi)型參數(shù)都可標(biāo)記為協(xié)變量或者逆變量,這樣我們即可將泛型委托類(lèi)型的一個(gè)變量轉(zhuǎn)型為同一個(gè)委托類(lèi)型的另一個(gè)變量,或者的泛型參數(shù)類(lèi)型不同。
不變量:表示泛型類(lèi)型參數(shù)不能更改。
逆變量:表示泛型類(lèi)型參數(shù)可以從一個(gè)基類(lèi)更改為該類(lèi)的派生類(lèi)。在C#中,用in關(guān)鍵字標(biāo)記逆變量形式的泛型類(lèi)型參數(shù),逆變量泛型參數(shù)只能出現(xiàn)在輸入位置.
協(xié)變量:表示泛型類(lèi)型參數(shù)可以從一個(gè)派生類(lèi)更改為它的基類(lèi),在C#中,用out標(biāo)記協(xié)變量形式的泛型類(lèi)型參數(shù).協(xié)變量只能出現(xiàn)在輸出位置.例如:方法返回值類(lèi)型.
public Delegate TResult Func
(T arg);
在上面這行代碼中,泛型類(lèi)型參數(shù) T 用in關(guān)鍵字標(biāo)記,使它成為了一個(gè)逆變量,而TResult用out 關(guān)鍵字標(biāo)記,這使他成為了一個(gè)協(xié)變量.
在使用要獲取泛型參數(shù)和返回值的委托時(shí),盡量使用逆變性和協(xié)變性指定in和out關(guān)鍵字.在使用具有泛型類(lèi)型參數(shù)的接口也可將它的類(lèi)型參數(shù)標(biāo)記為逆變量和協(xié)變量,如下代碼:
復(fù)制代碼
1 public interface IEnumerator: IEnumerator 2 { 3 Boolean MoveNext(); 4 T Current{get;} 5 } 6 // count方法接受任意類(lèi)型的參數(shù) 7 public int count(IEnumerablecoll){} 8 9 //調(diào)用count 傳遞一個(gè)IEnumerable10 int i = count(new[]{"Albin"});
復(fù)制代碼
深入委托 -> 委托揭秘
首先來(lái)聲明一個(gè)委托:
1 internal delegate void DelegateBackCall(int val);
通過(guò)查看委托反編譯:
通過(guò)反 編譯之后看到在DelegateBackCall來(lái)中有四個(gè)方法:分別為 : 構(gòu)造器、BeginInvoke、EndInvoke、Invoke 而且還能看到 DelegateBackCall 類(lèi)是繼承 system.MulticaseDelegate(其實(shí)所有的委托都是派生自它.本質(zhì)上來(lái)講:System.MulticaseDelegate是派生 自system.Delegate.而后者又派生自system.object),
在此我們還看到 在 第一個(gè)構(gòu)造函數(shù)中,有兩個(gè)參數(shù),一個(gè)是對(duì)象引用,一個(gè)是引用回調(diào)方法的一個(gè)整數(shù),其實(shí)所有委托中都有一個(gè)構(gòu)造器,而且構(gòu)造器的參數(shù)正如剛才所說(shuō)的(一個(gè)對(duì)象引用,一個(gè)引用回調(diào)方法的整數(shù)) 在構(gòu)造器的內(nèi)部,這兩個(gè)參數(shù)分別保存在_target(當(dāng) 委托對(duì)象包裝一個(gè)靜態(tài)方法時(shí),這個(gè)字段為null,當(dāng)委托對(duì)象包裝一個(gè)實(shí)例方法時(shí),這個(gè)字段引用的是回調(diào)方法要操作的對(duì)象.)和_method(一個(gè)內(nèi)部 的整數(shù)值,CLR用它來(lái)標(biāo)識(shí)要回調(diào)的方法) 這兩個(gè)私有字段呢給 ,此外構(gòu)造器還將_invocationList(構(gòu)造一個(gè)委托鏈時(shí),它可以引用一個(gè)委托數(shù)組,通常為null)字段設(shè)為null
每個(gè)委托對(duì)象實(shí)際都是一個(gè)包裝器,其中包裝了一個(gè)方法和調(diào)用該方法時(shí)要操作的一個(gè)對(duì)象
如下代碼:
1 DelegateBackCall DelegateInstance = new DelegateBackCall(Program.InstanceToConsole);2 3 DelegateBackCall DelegateStatic = new DelegateBackCall(StaticToMessage);
在此 DelegateInstance 和 DelegateStatic 變量引用兩個(gè)獨(dú)立的,初始化好的 DelegatebackCall委托對(duì)象. 在委托類(lèi)(Delegate)中定義了兩個(gè)只讀的公共實(shí)例屬性,Target和Method,當(dāng)我們給定一個(gè)委托對(duì)象引用可以查詢(xún)這些屬 性.,Target返回的就是我們之前說(shuō)的_Target字段中的值,指向回調(diào)方法要操作的對(duì)象,即如果是一個(gè)靜態(tài)的方法那么Target為 null,Method屬性有一個(gè)內(nèi)部轉(zhuǎn)換機(jī)制,可以將私有字段_methodPtr中的值轉(zhuǎn)為為一個(gè)MethodInfo對(duì)象并返回它.即我們所傳遞的 方法名.
在我們調(diào)用委托對(duì)象的變量時(shí),實(shí)際上編譯器所生成的代碼時(shí)調(diào)用的該委托對(duì)象的 Invoke方法,比如,在Counter函數(shù)中,Foo(val) 那么實(shí)際上編譯器為我們解析為 --> Foo.Invoke(val); Invoke是以一種同步的方式調(diào)用委托對(duì)象維護(hù)每一個(gè)方法.意思就是說(shuō):調(diào)用者必須等待調(diào)用完成才能繼續(xù)執(zhí)行.Invoke通過(guò)_Target和 _methodPtr在指定對(duì)象上調(diào)用包裝好的回調(diào)方法,Invoke方法的簽名與委托的簽名是一致的.
用委托回調(diào)很多方法 —> 委托鏈(多播委托)
委托鏈?zhǔn)怯晌袑?duì)象構(gòu)成的一個(gè)集合.利用委托鏈,可以調(diào)用集合中的委托所代表的任何方法.換句話說(shuō)就是一個(gè)委托對(duì)象可以維護(hù)一個(gè)可調(diào)用方法的列表而不是單獨(dú)一個(gè)方法,給一個(gè)委托對(duì)象添加多個(gè)方法時(shí),不用直接分配,在此C#編譯器為我們提供了重載 +=,-= 操作符,
那我們還是拿上一代代碼來(lái)舉例,代碼如下:
1 DelegateBackCall DelegateInstance = new DelegateBackCall(Program.InstanceToConsole);2 3 DelegateBackCall DelegateStatic = new DelegateBackCall(StaticToMessage);
這段代碼我們可以這樣來(lái)改裝一下:
1 DelegateBackCall DelegateFb = null;2 3 DelegateFb += new DelegateBackCall(Program.InstanceToConsole);4 5 DelegateFb += new DelegateBackCall(StaticToMessage);
其實(shí)上面這就是委托鏈(也叫多播委托),
那么我們來(lái)反編譯一下:
我們?cè)诜淳幾g之后發(fā)現(xiàn), +=操作的內(nèi)部是通過(guò) Delegate類(lèi)的靜態(tài)方法Combine將委托添加到鏈中的.
在此Combine會(huì)構(gòu)造一個(gè)新的委托對(duì)象,這個(gè)新的委托對(duì)象對(duì)它的私有字段 _target和_methodPtr進(jìn)行初始化, 同時(shí)_invocationList字段被初始化為引用一個(gè)委托對(duì)象數(shù)組,數(shù)組的第一個(gè)元素被初始化為引用包裝了 Program.InstanceToConsole 方法的委托,數(shù)組的第二個(gè)元素被初始化為引用了包裝了StaticToMessage方法的委托,然后再內(nèi)部會(huì)進(jìn)行遍歷每一個(gè)委托進(jìn)行輸出.
同理當(dāng)我們想移除委托對(duì)象集合中一個(gè)委托時(shí)我們可以通過(guò)-= 操作符.如下
DelegateBackCall delegateFb = null;
delegateFb -= new DelegateBackCall(new Program().InstanceToConsole);
delegateFb -= new DelegateBackCall(StaticToMessage);
那么反編譯后同樣的道理:
它的內(nèi)部是通過(guò)Remove函數(shù)來(lái)對(duì)委托進(jìn)行移除的.在Remove方法被調(diào)用 時(shí),它會(huì)遍歷所引用的那個(gè)委托對(duì)象內(nèi)部維護(hù)的委托數(shù)組,Remove通過(guò)查找其_target和methodPtr字段與第二個(gè)參數(shù)中的字段匹配的委托, 如果找到匹配的委托,并且在刪除之后數(shù)組中只剩余一個(gè)數(shù)據(jù)項(xiàng),就返回那個(gè)數(shù)據(jù)項(xiàng),如果找到,并且數(shù)組中還剩余多個(gè)數(shù)據(jù)項(xiàng),就新建一個(gè)委托對(duì)象 其中創(chuàng)建并初始化的_invocationList數(shù)組將引用原始數(shù)組中的所有數(shù)據(jù)項(xiàng)(被刪除的例外),如果刪除僅有的一個(gè)元素,那么Remove會(huì)返回 NULL.每次Remove只能刪除一個(gè)委托.不會(huì)刪除所有的.
在此說(shuō)明一個(gè)方法:GetInvocationList,這個(gè)方法操作一個(gè)從 MulticastDelegate派生的對(duì)象,返回一個(gè)由Delegate引用構(gòu)成的數(shù)組,其中每個(gè)引用都指向委托鏈中的一個(gè)委托.在其內(nèi) 部,GetInvocationList構(gòu)造并初始化一個(gè)數(shù)組,讓它的每個(gè)元素都引用鏈中的一個(gè)委托,然后返回對(duì)數(shù)組的一個(gè)引用,如果 _invocationList字段為null,返回?cái)?shù)組只有一個(gè)元素,那么它就是委托實(shí)例的本身.
如下代碼:
DelegateBackCall delegateFb = null;
delegateFb += new DelegateBackCall(new Program().InstanceToConsole);
delegateFb += new DelegateBackCall(StaticToMessage);private static void GetComponentReport(DelegateBackcall delegateFo)
{ if(delegatefo != null )
{
Delegate[] arrayDelegates = delegatefo.GetInvocationList(); foreach(DelegateBackCall delegateback in arrayDelegates){ //....省略 } }
}
委托定義太多? —> 泛型委托
在.NET Freamework 支持泛型,所以我們可以定義泛型委托,來(lái)減少委托聲明的個(gè)數(shù),這樣可以減少系統(tǒng)中類(lèi)型數(shù)目,同時(shí)也可以簡(jiǎn)化編碼.
在.NET freamework中為我們提供了17個(gè)Action委托,它們從無(wú)參數(shù)一直到最多16個(gè)參數(shù),對(duì)于開(kāi)發(fā)來(lái)說(shuō)應(yīng)該是足夠用的了.
除此之外 .NET FreameWork 還提供了17個(gè)Function函數(shù),它們?cè)试S回調(diào)方法返回一個(gè)值.
使用獲取泛型實(shí)參和返回值的委托時(shí),可利用逆變和協(xié)變,
委托的簡(jiǎn)潔寫(xiě)法 -> 1:Lambda表達(dá)式、匿名函數(shù)的方式來(lái)代替定義回調(diào)函數(shù)。
實(shí)際上這些的寫(xiě)法可歸納為C#的語(yǔ)法糖,它為我們程序員提供了一種更簡(jiǎn)單,更可觀方式.
例如:
不需要定義回調(diào)函數(shù),直接使用Lambda表達(dá)式的形式,創(chuàng)建匿名函數(shù)來(lái)執(zhí)行方法體.
Private Static Void CallBackNewDelegateObject()
{
ThreadPool.QueueUserWorkItem( o => Console.WriteLine(o) ,5);
}
傳給QueueUserWorkItem方法的第一個(gè)實(shí)參是一個(gè)Lambda表達(dá) 式,Lambda表達(dá)式可在編譯器預(yù)計(jì)會(huì)看到一個(gè)委托的地方使用,編譯器看到這個(gè)Lambda表達(dá)式之后會(huì)在類(lèi)中自動(dòng)定義一個(gè)新的私有方法,這個(gè)方法稱(chēng)為 匿名方法,所謂匿名方法,并不是沒(méi)有名字,它的名字是由編譯器自動(dòng)創(chuàng)建的.在此說(shuō)明一點(diǎn),匿名函數(shù)是被標(biāo)記為Static,并且是private 這是因?yàn)榇a沒(méi)有訪問(wèn)任何實(shí)例成員,不過(guò)類(lèi)中可以引用任何靜態(tài)字段或靜態(tài)方法.從而課件匿名函數(shù)的性能是比實(shí)例方法效率高的,因?yàn)樗恍枰~外的this 參數(shù).
2:簡(jiǎn)化語(yǔ)法 -> 局部變量不需要手動(dòng)的包裝到類(lèi)中即可傳給回調(diào)方法
public static void UsingLocalVariablesInTheCallBack(int num)
{
int[] i = new int[num]; 4
AutoResetEvent done = new AutoResetEvent(false);
for (int n = 0; n < i.Length; n++)
{
ThreadPool.QueueUserWorkItem(obj =>
{
int nums = (Int32)obj;
i[nums] = nums * nums;
if (Interlocked.Decrement(ref num) == 0)
{
done.Set();
}
}, n);
}
done.WaitOne();
for (int n = 0; n < i.Length; n++)
{
Console.WriteLine("Index {0},i {1}", n, i[n]);
}
}
在Lambda表達(dá)式的方法體中,如果這個(gè)方法體是一個(gè)單獨(dú)的函數(shù),那么我們?nèi)绾?的將變量的值傳到方法中呢?那么現(xiàn)在在我們的匿名函數(shù)方法體中的代碼就需要抽出到一個(gè)單獨(dú)的類(lèi)中,然后類(lèi)通過(guò)字段賦值每一個(gè)值,然 后 UsingLocalVariablesInTheCallBack 方法必須構(gòu)造這個(gè)類(lèi)的一個(gè)實(shí)例,用方法定義的局部變量的值來(lái)初始化這個(gè)實(shí)例中的字段,然后構(gòu)造綁定到實(shí)例方法的委托對(duì)象。
本文出自 51CTO “Albin” 博客
更多知識(shí)請(qǐng)進(jìn)入【濟(jì)寧果殼學(xué)院】