详解委托
委托和事件在 .Net Framework 中的應用非常廣泛,然而,較好地理解委托和事件對很多接觸C#時間不長的人來說并不容易。它們就像是一道檻兒,過了這個檻的人,覺得真是太容易了,而沒有過去的人每次見到委托和事件就覺得心里堵得慌,渾身不自在。本章將由淺入深地講述什么是委托、為什么要使用委托、事件的由來等。
7.1 理解委托
7.1.1 將方法作為方法的參數
先不管這個標題是如何的繞口,也不管委托究竟是個什么東西,來看下面這兩個最簡單的方法,它們不過是在屏幕上輸出一句問候的話語:
public void GreetPeople(string name)
{
??? //做某些額外的事情,比如初始化之類,此處略
??? EnglishGreeting(name);
}
public void EnglishGreeting(string name)
{
??? Console.WriteLine(string.Format("Hello,{0}", name));
}
暫且不管這兩個方法有沒有什么實際意義。GreetPeople 用于向某人問好,當傳遞代表某人姓名的name 參數值如“歐旭”時,在這個方法中,將調用EnglishGreeting方法,再次傳遞name 參數值進入EnglishGreeting方法中,然后輸出 “Hello,歐旭”。
現在假設這個程序需要進行全球化,輸出的問候語需要中文形式,怎么辦呢?解決辦法是再加個中文版的問候方法:
public void ChineseGreeting(string name)
{
??? Console.WriteLine(string.Format("您好,{0}", name));
}
此時,GreetPeople方法也需要改一改了,用來判斷到底用哪個版本的Greeting 問候方法合適。在進行這個之前,我們最好再定義一個枚舉作為判斷的依據:
public enum Language
{
??? English, Chinese
}
GreetPeople方法修改如下:
public void GreetPeople(string name,Language language)
{
??? //做某些額外的事情,比如初始化之類,此處略
??? switch (language)
??? {
??????? case Language.Chinese:
??????????? ChineseGreeting(name); break;
??????? case Language.English:
??????????? EnglishGreeting(name); break;
??? }
}
以上方法盡管解決了問題,但這個解決方案的可擴展性很差,如果日后需要再添加韓文版、日文版,就不得不反復修改枚舉和GreetPeople()方法,以適應新的需求。
在考慮新的解決方案之前,先看看 GreetPeople的方法簽名:
public void GreetPeople(string name,Language language)
僅看 string name,在這里,string 是參數類型,name 是參數變量,當賦給name字符串“歐旭”時,它就代表“歐旭”這個值;當賦給它“ouxu”時,它又代表著“ouxu”這個值。然后,我們可以在方法體內對這個name 進行其他操作。
如果再仔細想想,假如GreetPeople()方法可以接受一個參數變量,這個變量可以代表另一個方法,當我們給這個變量賦值EnglishGreeting的時候,它代表著 EnglsihGreeting() 這個方法;當我們給它賦值ChineseGreeting的時候,它又代表著ChineseGreeting()方法。我們將這個參數變量命名為 MakeGreeting,那么可以如同給name 賦值時一樣,在調用 GreetPeople()方法的時候,給這個MakeGreeting參數也賦上值ChineseGreeting 或者EnglsihGreeting 等。然后,我們在方法體內,也可以像使用別的參數一樣使用MakeGreeting。但是,由于MakeGreeting 代表著一個方法,它的使用方式應該和它被賦的方法(比如ChineseGreeting)是一樣的,比如:
MakeGreeting(name);
再有了以上思路以后,我們現在就來改改GreetPeople()方法,那么它應該是如下樣子:
public void GreetPeople(string name, *** MakeGreeting)
注意到***,這個位置通常放置的應該是參數的類型,但到目前為止,我們僅僅是想到應該有個可以代表方法的參數,并按這個思路去改寫GreetPeople方法,現在就出現了一個大問題:這個代表著方法的MakeGreeting參數應該是什么類型的?
注意:這里已不再需要枚舉了,因為在給MakeGreeting賦值的時候動態地決定使用哪個方法,是ChineseGreeting 還是 EnglishGreeting,而在這個兩個方法內部,已經對使用“Hello”還是“你好”作了區分。
到此,要解決GreetPeople(string name, *** MakeGreeting)方法中的***參數類型問題,就必須使用委托機制。但講述委托之前,我們再看看MakeGreeting 參數所能代表的 ChineseGreeting()和EnglishGreeting()方法的簽名:
public void EnglishGreeting(string name)
public void ChineseGreeting(string name)
如同name 可以接受string 類型的“true”和“1”,但不能接受bool 類型的true 和int 類型的1 一樣。MakeGreeting 的參數類型定義應該能夠確定MakeGreeting可以代表的方法種類,再進一步講,就是MakeGreeting可以代表的方法的參數類型和返回類型。
于是,委托出現了:它定義了MakeGreeting 參數所能代表的方法的種類,也就是MakeGreeting參數的類型。
public delegate void GreetingDelegate(string name);
可以與上面EnglishGreeting()方法的簽名對比一下,除了加入了delegate 關鍵字以外,其余的完全一樣。現在,讓我們再次改動GreetPeople()方法,如下所示:
public void GreetPeople(string name, GreetingDelegate MakeGreeting)
{
??? //做某些額外的事情,比如初始化之類,此處略
??? MakeGreeting(name);
}
如你所見,委托GreetingDelegate 出現的位置與 string 相同,string 是一個類型,那么GreetingDelegate 應該也是一個類型,或者叫類(Class)。但是委托的聲明方式和類卻完全不同,實際上,委托在編譯的時候確實會編譯成類。因為delegate 是一個類,所以在任何可以聲明類的地方都可以聲明委托。這個范例的完整代碼如下:
using System;
?
namespace 委托和事件
{
??? public delegate void GreetingDelegate(string name);//定義委托
??? public? class MyClass
??? {
??????? public static void Main()
??????? {
??????????? GreetPeople("歐旭", EnglishGreeting);
??????????? GreetPeople("ouxu", ChineseGreeting);
??????? }
??????? public void GreetPeople(string name, GreetingDelegate MakeGreeting)
??????? {
??????????? //做某些額外的事情,比如初始化之類,此處略
??????????? MakeGreeting(name);
??????? }
??????? public void EnglishGreeting(string name)
??????? {
??????????? Console.WriteLine(string.Format("Hello,{0}", name));
??????? }
??????? public void ChineseGreeting(string name)
??????? {
??????????? Console.WriteLine(string.Format("您好,{0}", name));
??????? }
??? }
}
輸出為:
Hello,歐旭
你好,ouxu
到此,可以對委托作一個總結:
委托是一種類型,它定義了能代表一類方法的類型,使得可以將該類方法當作另一個方法的參數來進行傳遞,這種將方法動態地賦給參數的做法,可以避免在程序中大量使用If‐Else(Switch)語句,同時使得程序具有更好的可擴展性。
7.1.2 將方法綁定到委托
?????? 在上面的例子中,我們不一定要直接在GreetPeople()方法中給 name 參數賦值,可以像這樣使用變量:
public static void Main()
{
??? string name1 ,name2;
??? name1 = "歐旭";
??? name2 = "ouxu";
??? GreetPeople(name1, EnglishGreeting);
??? GreetPeople(name2, ChineseGreeting);
}
而既然委托GreetingDelegate 和類型string的地位一樣,都是定義了一種參數類型,那么,我們也可以如下的方式使用委托:
public static void Main()
{
??? string name1,name2;
??? GreetingDelegate delegate1, delegate2;
??? name1 = "歐旭";
??? name2 = "ouxu";
??? delegate1=EnglishGreeting;
??? delegate2=ChineseGreeting;
??? GreetPeople(name1,delegate1);
??? GreetPeople(name2,delegate2);
}
以上程序運行正常。但是要注意,委托不同于string的一個特性:可以將多個方法賦給同一個委托,或者叫將多個方法綁定到同一個委托,當調用這個委托的時候,將依次調用其所綁定的方法。在這個例子中,語法如下:
public static void Main()
{
??? GreetingDelegate delegate1;
??? delegate1 = EnglishGreeting; // 先給委托類型的變量賦值
??? delegate1 += ChineseGreeting; // 給此委托變量再綁定一個方法
??? GreetPeople("ouxu", delegate1);
}
輸出為:
Hello,ouxu
你好,ouxu
實際上,我們也可以繞過GreetPeople 方法,通過委托來直接調用EnglishGreeting 和ChineseGreeting:
public static void Main()
{
??? GreetingDelegate delegate1;
??? delegate1 = EnglishGreeting;
??? delegate1 += ChineseGreeting;
??? //GreetPeople("ouxu", delegate1);
??? delegate1("ouxu");
}
注意這里,第一次用的“=”,是賦值的語法;第二次,用的是“+=”,是綁定的語法。如果第一次就使用“+=”,將出現“使用了未賦值的局部變量”的編譯錯誤。
我們也可以使用下面的代碼來這樣簡化這一過程:
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting;
既然可以給委托綁定一個方法,那么也應該有辦法取消方法的綁定,很容易想到,這個語法是“‐=”:
public static void Main()
{
??? GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
??? delegate1 += ChineseGreeting;
??? GreetPeople("ouxu", delegate1);
??? Console.WriteLine("----------------------------------------------------------------------------------------");
??? delegate1 -= EnglishGreeting;
??? GreetPeople("ouxu", delegate1);
}
輸出為:
Hello,ouxu
你好,ouxu
----------------------------------------------------------------------------------------
你好,ouxu
讓我們再次對委托作個總結:使用委托可以將多個方法綁定到同一個委托變量,當調用此變量時(這里用“調用”這個詞,是因為此變量指向一個方法),可以依次調用所有綁定的方法。如果把多個方法綁定到同一個委托變量上,這種委托稱為多播委托。在多播委托中,委托定義返回值通常為void,這是因為可以給委托變量綁定多個方法,如果定義了返回值,那么多個方法都會返回值,結果就是后面一個返回的方法值將前面的返回值覆蓋掉,因此,實際上只能獲得最后一個方法調用的返回值,多播委托中定義非void返回值沒有多大意義。
7.1.3 匿名方法
在之前的委托學習中,要想使委托工作,方法必須已經存在,即之前的EnglishGreeting()和ChineseGreeting()是事先定義的。但使用委托還有另外一種方式:即通過匿名方法。匿名方法是用作委托參數的一個代碼塊。
用匿名方法定義委托的語法與前面的定義并沒有區別。但在實例化委托時,就有區別了。下面是一個非常簡單的應用程序,說明了如何使用匿名方法:
namespace 委托和事件
{
??? public delegate void GreetingDelegate(string name);//定義委托
?
??? public class MyClass
??? {
??????? public static void Main()
??????? {
??? ????????GreetingDelegate delegate1 = delegate(string name)
??????????? {
??????????????? Console.WriteLine(string.Format("您好,{0}", name));
??????????? };
??????????? delegate1("ouxu");
??????? }?????
??? }
}
在以上程序中,在定義委托變量delegate1時,不是傳送已知的方法名,而是使用一個簡單的代碼塊:它前面是關鍵字delegate,后面是一個參數:
delegate(string name)
{
?? Console.WriteLine(string.Format("您好,{0}", name));
};
匿名方法的優點是減少了要編寫的代碼,方法僅在使用委托時才定義,這有助于降低代碼的復雜性。使用匿名方法時,雖然我們沒有指定固定的方法名,但編譯器在編譯此段代碼時仍定義了一個帶有方法名的方法,但我們不需要知道這個方法。
轉載于:https://www.cnblogs.com/gyouxu/archive/2013/04/17/3027382.html
總結
- 上一篇: uboot nand erase 的显示
- 下一篇: Windows Socket 编程