從簡單的例子理解委托
吳劍 2012-06-11
原創文章,轉載必需注明出處:http://www.cnblogs.com/wu-jian/
吳劍 http://www.cnblogs.com/wu-jian
前言
.Net開發基礎系列文章,從簡單的例子理解委托。對自己之前寫過的代碼備忘,如能給人予幫助,不甚榮幸。個人能力有限,如有差錯或不足,請及時指正。
吳劍 http://www.cnblogs.com/wu-jian
從一個簡單的例子開始
金城武演的有部老電影叫《薰衣草》,里面有個情節大概是這樣的:小金收客戶的錢,然后代表客戶去向不同的人Say I love you。
一開始他的客戶都是中國人,只需要說中文,如下代碼示例,很簡單,支持所有中國客戶:
public class LoveManager { public void Love(string name) { Console.WriteLine("我愛你, {0}", name); } }
class Program { static void Main(string[] args) { LoveManager loveManager = new LoveManager(); loveManager.Love("張曼玉"); } }
執行結果:
我愛你, 張曼玉
我留意到后來電影里出現了外國客戶,我想代碼應該是這樣:
//枚舉,可擴展多語種 public enum Language { English, Chinese }
public class LoveManager { public void Love(string name, Language lang) { switch (lang) { case Language.Chinese: loveChinese(name); break; case Language.English: loveEnglish(name); break; } } //漢語客戶專用 public void LoveChinese(string name) { Console.WriteLine("我愛你, {0}", name); } //英語客戶專用 public void LoveEnglish(string name) { Console.WriteLine("I love you, {0}", name); } }
class Program { static void Main(string[] args) { LoveManager loveManager = new LoveManager(); loveManager.Love("張曼玉", Language.Chinese); loveManager.Love("Madonna", Language.English); } }
執行結果:
我愛你, 張曼玉
I love you, Madonna
OK,現在張曼玉能聽懂“我愛你”,Madonna能聽懂“I love you”。雖然支持了英漢雙語表白,但以后還有法國客戶,葡萄牙客戶,阿拉伯客戶怎么辦?每擴展一個語種除了添加這個語種“我愛你”的方法,還得擴展枚舉,擴展LoveManager.Love(),確實有些繁瑣。
C語言時代:指針
此時,不得不提到C語言中大名鼎鼎的指針。指針允許把一個函數的地址作為參數傳遞給另一個函數,這個特性在以后的各種高級語言中得到了擴展和加強。先看如下C代碼:
#include <stdio.h> //接受一個指針類型的參數 void func1(void(*p)(void)){ printf("this is func1\r\n"); //通過指針調用函數 p(); } void func2(){ printf("this is func2\r\n"); } int main() { //將func2地址作為參數傳遞 func1(func2); return 0; }
執行結果:
this is func1
this is func2
在.Net中能不能像C語言一樣,把函數作為一個參數傳遞並且調用呢?
//這段代碼並不能被執行,但如果在.Net中可以這樣寫的話問題就會簡單很多
Love("張曼玉", LoveChinese); Love("Madonna", LoveEnglish);
.Net中更完美的解決方案:委托
在.Net中不但可以像C語言一樣將函數作為參數傳遞,並且.Net提供了類型安全機制和更加強大的功能,如下提供了使用委托的完整代碼示例:
using System; namespace DelegateDemo { //定義委托 public delegate void LoveDelegate(string name); public class LoveManager { public void Love(string name, LoveDelegate loveDelegate) { loveDelegate(name); } //漢語客戶專用 public void LoveChinese(string name) { Console.WriteLine("我愛你, {0}", name); } //英語客戶專用 public void LoveEnglish(string name) { Console.WriteLine("I love you, {0}", name); } } class Program { static void Main(string[] args) { LoveManager loveManager = new LoveManager(); loveManager.Love("張曼玉", loveManager.LoveChinese); loveManager.Love("Madonna", loveManager.LoveEnglish); } } }
執行結果:
我愛你, 張曼玉
I love you, Madonna
定義委托
public delegate void LoveDelegate(string name);
在C#中委托使用特有的關鍵字 delegate 來定義,在delegate之后緊跟的是函數簽名。為了確保類型安全,.Net中的委托要求函數具有相同的簽名,比如 func(int p) 和func(string p)不能使用同一個委托,因為它們的參數類型不一樣。
通過ILDasm.exe可以發現,定義委托的那行代碼實際在編譯時會自動生成一個類,如果要還原這個類,代碼會是這樣:
public class LoveDelegate : System.MulticastDelegate { //構造器 public LoveDelegate(Object obj, IntPtr method); //原型 public virtual void Invoke(string name); //異步回調 public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object obj); public virtual void EndInvoke(IAsyncResult result); }
因此,委托實際上就是一個類,它繼承至System.MulticastDelegate,凡是可以定義類的地方,都可以定義委托。
委托的構造函數
LoveManager loveManager = new LoveManager(); //編譯不能通過,委托必須使用帶有一個參數的構造函數 //LoveDelegate loveDelegate = new LoveDelegate(); LoveDelegate loveDelegate = new LoveDelegate(loveManager.LoveChinese); loveDelegate("吳劍");
與類不同的是,委托必須使用帶有一個參數的構造函數。
委托推斷語法
LoveManager loveManager = new LoveManager(); //等同於:LoveDelegate loveDelegate = new LoveDelegate(loveManager.LoveChinese); LoveDelegate loveDelegate = loveManager.LoveChinese; loveDelegate("吳劍");
委托與方法進行綁定
回到上面的例子,有一天一富二代找到小金,說錢不是問題,你去張曼玉樓下,用中文喊一遍,再用英文喊一遍。
static void Main(string[] args) { LoveManager loveManager = new LoveManager(); //定義委托變量 LoveDelegate delegate1; //變量初始化(用中文喊一遍) delegate1 = loveManager.LoveChinese; //綁定方法(用英文再喊一遍) delegate1 += loveManager.LoveEnglish; delegate1("張曼玉"); }
執行結果:
我愛你, 張曼玉
I love you, 張曼玉
我們可以用 += 將多個方法綁定到一個委托,也可以使用 -= 移除方法與委托的綁定。調用時每個方法按順序迭代處理,並返回最后一個方法的值(如果委托定義了返回值)。
匿名方法
客戶的需求總是千變萬化,一個客戶跟小金說,我要跟曼玉表白,除了用中英文,能不能后面再給我加一句,曼玉一聽到這句准會答應我。
LoveManager loveManager = new LoveManager(); LoveDelegate loveDelegate = loveManager.LoveEnglish; loveDelegate += loveManager.LoveChinese; loveDelegate += delegate(string name) { Console.WriteLine("{0}, 還記得大明湖畔的夏雨荷嗎?", name); }; loveDelegate("曼玉");
執行結果:
I love you, 曼玉
我愛你,曼玉
曼玉,還記得大明湖畔的夏雨荷嗎?
針對這位特殊客戶使用了匿名方法,不是每個人示愛的時候都會提到大明湖畔的夏雨荷,也就是這位特殊客戶使用一次而以,所以沒有必要定義一個獨立的方法。使用匿名方法可以減少編碼量,降低代碼復雜度。
Lambda(λ)表達式
C# 3.0為匿名方法提供了Lambda表達式,如下代碼執行結果與上面的示例完全一致:
LoveManager loveManager = new LoveManager(); LoveDelegate loveDelegate = loveManager.LoveEnglish; loveDelegate += loveManager.LoveChinese; //用紅色字體標出了Lambda表達式部分
loveDelegate += name => { Console.WriteLine("{0}, 還記得大明湖畔的夏雨荷嗎?", name); }; loveDelegate("曼玉");
=>為Lambda運算符,運算符左邊列出匿名方法需要的參數,可以這樣使用:
(string param1, int param2)
也可以:
(param1, param2)
如示例代碼只有一個參數還可以去掉括號:
param1
Lambda表達式右邊為匿名方法實現代碼,如果實現代碼只有一行,還可以刪除花括號和return語句,因為編譯器會自動添加。
DEMO下載
開發環境:Windows 7,.Net Framework4.0,Visual Studio 2010
<全文完>
吳劍 http://www.cnblogs.com/wu-jian
作者: 吳劍
出處: http://www.cnblogs.com/wu-jian/
本文版權歸作者所有,歡迎轉載,但必需注明出處,並且在轉載頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
