解析.NET中容易混淆的委托與接口
昨天在和一位朋友討論到委托與接口的問(wèn)題,一開(kāi)始我覺(jué)得很不可思議,這兩個(gè)東西的概念怎么會(huì)混淆呢?要混淆也是接口和抽象類(lèi),委托和事件相混淆啊!但是著我的一個(gè)例子我馬上意識(shí)到很有可能因?yàn)槲覍⒁憩F(xiàn)的這個(gè)例子,讓很多朋友混淆了委托與接口的用途.所以我想通過(guò)這篇文章試圖說(shuō)明白委托和接口的概念和用途,其實(shí)他們倆的差別還是很大的.
本文適合對(duì)委托和接口概念非常了解的朋友,并且歡迎各位朋友與Snake一起探討有關(guān)這方面的知識(shí). 本文不適合對(duì)委托和接口概念或用途了解一知半解(模糊)的朋友,這篇文章可能會(huì)對(duì)您產(chǎn)生誤導(dǎo),請(qǐng)千萬(wàn)別看. (本文原文是一篇沒(méi)有好好排版過(guò)的email,我這里將會(huì)部分摘抄,部分改進(jìn),如果有什么地方您覺(jué)得莫名其妙,我將非常感謝您的指正!)
在文章正式開(kāi)始之前我需要將MSDN上對(duì)委托和接口的內(nèi)容放上來(lái),作為文章之基.
委托:
委托是一種定義方法簽名的類(lèi)型。當(dāng)實(shí)例化委托時(shí),您可以將其實(shí)例與任何具有兼容簽名的方法相關(guān)聯(lián)。您可以通過(guò)委托實(shí)例調(diào)用方法。
委托用于將方法作為參數(shù)傳遞給其他方法。事件處理程序就是通過(guò)委托調(diào)用的方法。您可以創(chuàng)建一個(gè)自定義方法,當(dāng)發(fā)生特定事件時(shí)某個(gè)類(lèi)(例如 Windows 控件)就可以調(diào)用您的方法.
委托具有以下特點(diǎn):
委托類(lèi)似于 C++ 函數(shù)指針,但它們是類(lèi)型安全的。
委托允許將方法作為參數(shù)進(jìn)行傳遞。
委托可用于定義回調(diào)方法。
委托可以鏈接在一起;例如,可以對(duì)一個(gè)事件調(diào)用多個(gè)方法。
方法不必與委托簽名完全匹配。有關(guān)更多信息,請(qǐng)參見(jiàn)在委托中使用變體(C# 和 Visual Basic)。
C# 2.0 版引入了匿名方法的概念,此類(lèi)方法允許將代碼塊作為參數(shù)傳遞,以代替單獨(dú)定義的方法。C# 3.0 引入了 Lambda 表達(dá)式,利用它們可以更簡(jiǎn)練地編寫(xiě)內(nèi)聯(lián)代碼塊。匿名方法和 Lambda 表達(dá)式(在某些上下文中)都可編譯為委托類(lèi)型。這些功能統(tǒng)稱(chēng)為匿名函數(shù)。有關(guān) Lambda 表達(dá)式的更多信息,請(qǐng)參見(jiàn)Anonymous Functions (C# Programming Guide)。
接口:
接口描述的是可屬于任何類(lèi)或結(jié)構(gòu)的一組相關(guān)功能。接口可由方法、屬性、事件、索引器或這四種成員類(lèi)型的任意組合構(gòu)成。接口不能包含字段。接口成員一定是公共的。
當(dāng)類(lèi)或結(jié)構(gòu)繼承接口時(shí),意味著該類(lèi)或結(jié)構(gòu)為該接口定義的所有成員提供實(shí)現(xiàn)。接口本身不提供類(lèi)或結(jié)構(gòu)能夠以繼承基類(lèi)功能的方式繼承的任何功能。但是,如果基類(lèi)實(shí)現(xiàn)接口,派生類(lèi)將繼承該實(shí)現(xiàn)。
類(lèi)和結(jié)構(gòu)可以按照類(lèi)繼承基類(lèi)或結(jié)構(gòu)的類(lèi)似方式繼承接口,但有兩個(gè)例外:
類(lèi)或結(jié)構(gòu)可繼承多個(gè)接口。
類(lèi)或結(jié)構(gòu)繼承接口時(shí),僅繼承方法名稱(chēng)和簽名,因?yàn)榻涌诒旧聿话瑢?shí)現(xiàn)。
接口具有下列屬性:
接口類(lèi)似于抽象基類(lèi):繼承接口的任何非抽象類(lèi)型都必須實(shí)現(xiàn)接口的所有成員。
不能直接實(shí)例化接口。
接口可以包含事件、索引器、方法和屬性。
接口不包含方法的實(shí)現(xiàn)。
類(lèi)和結(jié)構(gòu)可從多個(gè)接口繼承。
接口自身可從多個(gè)接口繼承。
正文開(kāi)始
在寫(xiě)這些文字的時(shí)候我又將以上的各個(gè)概念熟悉了一遍,以防自己把自己忽悠混淆了.所以不適合群眾請(qǐng)盡快退散.另外如果您看完上面的定義和特征后就從兩者的混淆中走了出來(lái),您也可以嘗試?yán)^續(xù)往下看.
首先,關(guān)于委托的用法,我們可以這樣使用:
以下是代碼片段:
- public int Calculate(Func del) { int a = 1, b = 2; return del(a, b); }
我們可以通過(guò)傳不同的Func來(lái)改變整個(gè)方法的結(jié)果.
以下是代碼片段:
- public int Add(int a, int b)
- { return a + b; }
- public int Sub(int a, int b)
- { return a - b; }
- //調(diào)用方法如下
- public void TestMethod()
- { int result = Calculate(Add);
- //the result is 3 int anotherResult = Calculate(Sub);
- //the result is -1
- }
首先我在Calculate方法中已經(jīng)確定了2個(gè)數(shù)的值,并且包括在該方法當(dāng)中.在輸出結(jié)果的時(shí)候能明顯看出傳遞的委托不同,其結(jié)果也不同.我們使用委托來(lái)改變方法的執(zhí)行內(nèi)容,我們不但可以改變其方法的內(nèi)容,也可以在執(zhí)行該方法的時(shí)候順便做點(diǎn)什么(比如說(shuō)做個(gè)日志記錄).
噢,可能您覺(jué)得二者容易混淆的地方在于..我還是舉個(gè)例子比較好解釋.
以下是代碼片段:
- public interface ICal
- { int Calculate(int a, int b); }
- //有多個(gè)類(lèi)實(shí)現(xiàn)了ICal接口.
- public class Add : ICal
- { public int Calculate(int a, int b)
- { return a + b; } } public class Sub : ICal
- { public int Calculate(int a, int b)
- { return a - b; } }
- //然后通過(guò)調(diào)用不同類(lèi)來(lái)獲取不同的方法
- public static void Main()
- {
- ICal cal = new Add();
- //ICal=new Sub();
- Console.Write(cal.Calculate(1, 2));
- }
講解一下,通過(guò)上面的例子我們可以知道在創(chuàng)建一個(gè)具有計(jì)算功能(Calculate)的接口ICal之后,產(chǎn)生了兩個(gè)具有計(jì)算功能的具體類(lèi),分別是Add和Sub.為了要獲得結(jié)果,我們創(chuàng)建了一個(gè)需要有計(jì)算功能的”坑”,并賦予能與此”坑”相匹配的類(lèi)Add(或Sub),***從該坑中調(diào)用Calculate的結(jié)果就行.
貌似說(shuō)的過(guò)去?好,那么我至少要讓你覺(jué)得有個(gè)適用范圍吧!看下面的例子.
比如有個(gè)Person類(lèi)的數(shù)組arr.這時(shí)候我們可以通過(guò)委托的方法實(shí)現(xiàn)arr的排序.可是系統(tǒng)怎么知道2個(gè)Person哪個(gè)排在前面,哪個(gè)該排在后面?這時(shí)候我們就可以傳進(jìn)一個(gè)委托來(lái)告訴系統(tǒng)Peron類(lèi)的大小.
以下是代碼片段:
- arr.Sort(p => { p.ID });
該lambda表達(dá)式意思是丟給該Sort方法一個(gè)排序的Key(此key能夠進(jìn)行大小比較),那么Sort就可以根據(jù)此key來(lái)進(jìn)行比較.那通過(guò)接口呢?首先得創(chuàng)建一個(gè)繼承自IComparer的類(lèi),我就拿本身繼承它吧.
好吧,它本來(lái)是很麻煩的:
以下是代碼片段:
- private int SortDelegate(Person p)
- { return p.ID; }
- public void TestMethod()
- { arr.Sort(new Func(SortDelegate));
- }
但是我們要承認(rèn)C# 3.0帶給我們的便利.
現(xiàn)在,我們要讓Person類(lèi)實(shí)現(xiàn)接口的規(guī)定.
以下是代碼片段:
- public int Compare(Person x, Person y)
- {
- //假設(shè)person的ID是int類(lèi)型
- return x.ID - y.ID;
- }
那么我們的實(shí)現(xiàn)方法就可能是這樣:
以下是代碼片段:
- arr.Sort((new Person() as IComparer) comp);
不能再繼續(xù)舉例子了,我承認(rèn)我忽悠您了.這些看上去都可以的實(shí)現(xiàn)方法有本質(zhì)的區(qū)別!
首先我們看***個(gè)委托例子:在Calculate時(shí)我們的委托被允許使用了該方法內(nèi)的兩個(gè)變量a,b從而改變了整個(gè)方法的結(jié)果.在整個(gè)過(guò)程中委托時(shí)很被動(dòng)的,因?yàn)樗恢雷约簳?huì)在什么時(shí)候被觸發(fā).上面的例子很簡(jiǎn)單,使您沒(méi)有這種感覺(jué),而且前面說(shuō)過(guò)在方法執(zhí)行的時(shí)候當(dāng)委托被觸發(fā)我們可以干點(diǎn)別的,比如說(shuō)做個(gè)日志記錄什么的,此時(shí)接口有能力又不破壞方法本身運(yùn)行結(jié)構(gòu),又能做日志記錄嗎?顯然實(shí)現(xiàn)了接口的類(lèi)只能重寫(xiě)一遍該方法.
路人甲:那我在接口的實(shí)現(xiàn)中再調(diào)用一下原方法,***在方法的前面或后面加入日志記錄功能不就完了嗎?
Snake:殺雞焉用宰牛刀?且不說(shuō)再原方法的可行性,就算可行了,麻煩不說(shuō),萬(wàn)一這個(gè)方法執(zhí)行有多個(gè)階段,每個(gè)階段都要日志記錄呢?委托能深入方法,并且由方法控制它安放之地,讓委托能起到關(guān)鍵作用,此時(shí)作為接口大哥的牛刀也剔不干凈雞骨上的肉喲~.
其次說(shuō)接口的優(yōu)點(diǎn).我們前面可以看到委托能深入方法,也就是說(shuō)委托的關(guān)注群體是方法們,而接口關(guān)注的群體則是類(lèi)們.接口讓類(lèi)必須實(shí)現(xiàn)相同簽名的方法或?qū)傩?以便在程序中通過(guò)調(diào)用可變的方法.既然是因?yàn)轭?lèi)的關(guān)系,那么它的方法肯定是不可變的了,每個(gè)實(shí)現(xiàn)了該接口的類(lèi),即便功能差不多也要完完全全寫(xiě)一遍,但是類(lèi)的地盤(pán)大,肚子里的墨水多,雖然在Add類(lèi)中通過(guò)ICal可調(diào)用的方法也就一個(gè)Calculate(),但是在Calculate始終是Add類(lèi)的子民,所以該Calculate方法可以調(diào)用Add類(lèi)中所有能調(diào)用的資源.而如果是Sub類(lèi)的話,它的子民Caculate可調(diào)用的資源又與Add類(lèi)不盡相同,畢竟同是Calculate,國(guó)籍不同,文化和生活方式也不同嘛,哈哈.
而接口的能力卻是委托所不能企及的地方.它只能被方法藏在伸出,方法外一片藍(lán)天而它卻無(wú)能為力.如果讓類(lèi)比作一個(gè)國(guó)家,方法比作一個(gè)人,那么委托不就是深藏在人大腦內(nèi)的處理方式的思維嗎?不同的人,思維可以變,當(dāng)鄉(xiāng)下人看到城市中的高樓大廈不禁感嘆,可鄉(xiāng)下人在城市中生活習(xí)慣之后,高樓大廈又能怎樣,他早已習(xí)以為常.
***的論點(diǎn)有點(diǎn)晦澀,前面的例子具有誤導(dǎo)性,所以本篇文章需要讀懂個(gè)人認(rèn)為不是很容易,畢竟個(gè)人對(duì)于表達(dá)能力還是比較不自信的.希望各位同仁海涵.
【編輯推薦】
- 用C#實(shí)現(xiàn)HTTP協(xié)議下的多線程文件傳輸
- 淺談C++調(diào)用C#的DLL程序方法
- 利用C#實(shí)現(xiàn)任務(wù)欄通知窗口
- C#控件的閃爍問(wèn)題解決方法總結(jié)
- 淺析C#延遲加載的運(yùn)行機(jī)制和應(yīng)用場(chǎng)景