去年自學C#用的教程是入門級的《學通C#的24堂課》,教材裡面也沒有提到委托和事件,工作中也沒怎麼用到。後來一次在網上看了一些大牛的博客,讀完之後感覺懵懵懂懂,似懂非懂,過了兩三天之後,卻又全然忘記了。畢竟學習這事,溫故而知新,學了不用,自然忘得也很快。對於如我一樣的初學者來說,較好地理解委托和事件並是一件容易的事。其實掌握了的人,會覺得也沒什麼,而沒有掌握的人,每次見到委托和事件就會覺得很畏懼。前段時間看到張旭亮老師的博客中關於.NET 開發系列PPT中提到一個觀點,沒學會委托就等不會.NET。我深受激勵,所以這次下定決心認認真真學習了一下,並將自己的一些理解記錄下來。
委托,字面上的意思就是請別人幫忙做一些事情。讀完文章之後你會發現,委托是指向一個方法的指針,通過指定一個委托名稱,即可通過委托來調用方法。實際上與字面意思差不多。
什麼情況下適合使用委托
首先通過一個例子來說明什麼情況下使用委托。
以學生值日為例,值日生要做開門、擦黑板的工作。對應的方法形式如下:
public void OpenDoor(){ //開門具體實現 ... } public void CleanBlackBoard(){ //擦黑板具體實現 ... }
下面再寫一個值日的方法,要完成上述這些工作:
public void OnDuty(){ OpenDoor(); CleanBlackBoard();
}
這樣,值日生開門、擦黑板的工作就實現了。但是這種方法的擴展性和靈活性不是很好,假設現在值日生工作又增加了,需要關燈,方法如下:
public void TurnOffLight(){ //關燈的具體邏輯 ... }
我們就需要修改OnDuty方法:
public void OnDuty(){ OpenDoor(); CleanBlackBoard(); TurnOffLight(); }
上面的代碼沒有使用委托,但是還是實現了需要完成的工作。很容易發現,開門、擦黑板、關燈,雖然他們的方法名稱不同,但是它們具有相同的“形式”——它們都不獲取參數,也都沒有返回值(void),這正是委托可以發揮作用的時候。使用於這種形式匹配的一個委托。就可以引用任何工作的方法。我們可以聲明如下的一個委托:
public delegate void DoSomeWorkDelegate();
注意:
定義好委托之後,就可以創建它的一個實例,並使用 “+=”操作符,讓這個實例引用一個相匹配的方法。代碼如下:
public delegate void DoSomeWorkDelegate(); public DoSomeWorkDelegate doSomeWork; doSomeWork+=OpenDoor;
上例中的方法很簡單,既沒有返回值,又沒有參數。目的只是了解在何種情況下使用委托。下面通過一個帶參數的例子來說明繼續說明如何使用委托。
如何使用委托
此處以聽課為例,為了對比,同樣先不使用委托,先上代碼:
public void AttendClass(string name) { // 做某些額外的事情,比如初始化之類,此處略 ChineseClass(name); } public void ChineseClass(string name) { //具體聽語文課的邏輯 ... }
上面這段代碼表示。AttendClass 表示聽課,當我們傳遞代表學生姓名 name 參數,比如說“Jhon”,進去的時候,在這個方法中,將調用ChineseClass 方法,再次傳遞 Jhon 參數, 表示Jhon 參加了語文課。
假設現在開設了數學課,我們需要添加新的方法:
public void MathClass(string name){ //具體聽數學課的邏輯
... }
這時候 AttendClass 也要修改,在此之前先定義一個枚舉用作判斷:
public enum Subject{ Chinese,Math } public void AttendClass(string name, Subject sub){ //做某些額外的事情,比如初始化之類,此處略 swith(sub){ case Subject.Chinese:
ChineseClass(name); break; case Subject.Math: MathClass(name); break; } }
OK,現在問題解決了,誠如前一個例子所說,假如現在又要增加英語課,體育課,就不得不反復修改枚舉和 AttendClass 。可見程序的可拓展性很不好。
如前例,對於上課的方法,不論是什麼課,它們都具有相同的“形式”,這樣我們即可使用委托。
在考慮如何使用委托之前,我們先看看 AttendClass 的方法簽名:
public void AttendClass(string name, Subject sub)
我們僅看 string name,在這裡,string 是參數類型,name 是參數變量,當我們給字符串那麼賦值時,我們可以在方法體內對這個name進行其他操作.現在假如現在讓 AttendClass() 方法接受一個參數變量,這個變量可以代表另一個方法,當給這個變量賦值為 ChineseClass 時,它代表 ChineseClass() 這個方法,當給它賦值為 MathClass 時,他代表MathClass() 這個方法。就是說讓用一個參數來代方法。比如,把這個參數命名為,SubClass ,然後我們在方法體內,就像使用參數一樣使用SubClass。由於 SubClass 代表著一個方法,它的使用方式應該和它被賦值的方法(比如 ChineseClass)是一樣的,比如
SubClass(string name)
按照這樣的思路這樣,AttendClass(),就應該是這個樣子的:
public void AttendClass(string name,*** SubClass){ SubClass(name); }
注意到 *** ,這個位置通常放置的應該是參數的類型,現在就出現了一個問題:這個代表著方法的SubClass參數應該是什麼類型的?
如果我說答案就是委托。它定義了 SubClass 方法的種類,也就是 SubClass 方法參數的類型。如同 string 決定了AttendClass() 這個方法的第一個參數的類型一樣,委托決定 AttendClass 方法中的第二個參數的類型。
本例中委托的定義如下:
public delegate void ClassDelegate(string name);
現在再次改動 AttendClass 方法:
public void AttendClass(string name,ClassDelegate SubClass){ SubClass(name); }
委托ClassDelegate出現的位置與 string 相同,string是一個類型,那麼ClassDelegate 應該也是一個類型,或者叫類(Class)。但是委托的聲明方式和類卻完全不同。那麼本例完整有關上課的類的代碼如下:
public delegate void ClassDelegate(string name); public class OnClass{ //其它代碼 ... public void AttendClass(string name,ClassDelegate SubClass){ SubClass(name); } public void ChineseClass(string name){ ... } public void MathClass(string name){ ... } }
由此可見,委托是一種類型。它定義了方法的類型,實際上就是把方法當做變量。(注意與指針的區別)
那麼我們如何使用它呢,代碼如下:
private OnClass objOnClass; objOnClass=new OnClass(); string name1="xiaoming"; string name2="小張"; objOnClass.AttendClass(name1,ChineseClass); objOnClass.AttendClass(name2,MathClass);
上面的代碼表示 xiaoming 參加了語文課,小張參加了數學課。既然委托是一種類型。那麼我們也可以以下面的方式來運用委托
ClassDelegate delegate1,delegate2; delegate1=objOnClass.ChineseClass; delegate2=objOnClass.MathClass; string name1="xiaoming"; string name2="小張"; objOnClass.AttendClass(name1,delegate1); objOnClass.AttendClass(name2,delegate2);
還可以將多個方法付給同一個委托,下面的代碼表示小張既參加了語文課,又參加了數學課
ClassDelegate delegate3; delegate3=objOnClass.ChineseClass; delegate3+=objOnClass.MathClass; string name1="小張"; objOnCLass.AttendClass(name1,delegate3);
可以看到,為委托綁定方法我們使用 重載了的 += 這個符號。如果將上面的代碼中第一次綁定方法 delegate3=ChineseClass 改為 delegate3+=ChineseClass,將會出現“使用了未賦值的局部變量”的錯誤。我們可以像下面這樣使用委托,給它初始化賦值
ClassDelegate delegate3=new ClassDelegate(objOnClass.ChineseClass); string name1="xiaoming"; objOnClass.AttendClass(name1,delegate3);
我們甚至可以不用AttendClass這個方法,像下面這樣來使用委托
ClassDelegate delegate3=new ClassDelegate(objOnClass.ChineseClass); string name1="xiaoming"; delegate3(name1);
所以,可以將多個方法綁定到一個委托上面,調用的時候依次執行。