【IT168 技術(shù)】委托是一種引用方法的類型,。一旦為委托分配了方法,委托將與該方法具有完全相同的行為,。委托方法的使用可以像其他任何方法一樣,,具有參數(shù)和返回值,如下面的示例所示:
1 .定義 //Code in C#
public delegate int PerformCalculation(int x, int y); 與委托的簽名(由返回類型和參數(shù)組成)匹配的任何方法都可以分配給該委托,。 簡單理解Delegate委托(或代理)是一種數(shù)據(jù)類型:它的變量可以引用到某一個(gè)符合要求的方法上,,通過委托可以間接地調(diào)用該方法。 其實(shí).NET的委托類似于C語言的函數(shù)指針,,區(qū)別在于.NET委托是類型安全的,,這說明,C中的函數(shù)指針只不過是一個(gè)指向存儲(chǔ)單元的指針,,我們無法說出這個(gè)指針實(shí)際指向什么,。 2. 委托使用 使用委托的四部曲: 定義一種委托類型 委托執(zhí)行時(shí)要調(diào)用方法 定義一個(gè)委托實(shí)例 委托實(shí)例的調(diào)用 我們先定義一種委托類型如下: //自定義一種委托類型
publicdelegatevoid StringProcessor(string input);
然后我們再定義5中候選的委托方法如下:
void PrintString(string x)
void PrintInteger(int x)
void PrintTwoStrings(string x, string y) int GetStringLength(string x)
void PrintObject(object x)
大家猜猜看哪個(gè)和上面提供的委托類型簽名匹配(簽名匹配:參數(shù)類型,參數(shù)個(gè)數(shù)和返回類型匹配),。激動(dòng)時(shí)刻到了馬上公布答案,,和委托類型匹配的方法是PrintString和PrintObject,如果有不明白的請細(xì)細(xì)考慮一下委托匹配的條件—簽名匹配,。
圖1委托成功輸出 現(xiàn)在對委托有了一定的認(rèn)識,,接下來我們將介紹委托最經(jīng)常使用的地方—事件。 我們將從發(fā)送器和接受器的角度討論事件,,例如在UI編程中,,鼠標(biāo)單擊或鍵盤按鍵,,發(fā)送器就是.NET的CLR,注意事件發(fā)送器并不知道接收器是誰,,這符合面向?qū)ο蟮脑瓌t,,而且某個(gè)事件接收器有個(gè)方法處理該事件,這個(gè)時(shí)候就要委托,,如前面所講事件發(fā)送器對事件接收器一無所知,,通過委托作為一個(gè)中介,接收器把事件處理方法注冊到事件中,,這樣就實(shí)現(xiàn)了由發(fā)送器->委托->接收器的過程了,。 我們可以這樣認(rèn)為:委托是一個(gè)類,它定義了方法的類型,,使得可以將方法當(dāng)作另一個(gè)方法的參數(shù)來進(jìn)行傳遞,,這種將方法動(dòng)態(tài)地賦給參數(shù)的做法,可以避免在程序中大量使用If-Else(Switch)語句,,同時(shí)使得程序具有更好的可擴(kuò)展性,。
3 .自定義委托 前面話有點(diǎn)難以理解,接下來我們通過具體的例子分析一下何謂委托,,該如何實(shí)現(xiàn)委托?,F(xiàn)在不是很喜歡搞多國語言化的嗎?看看如何讓我們的程序會(huì)說多種語言吧! /// <summary>
/// the English speaker. /// </summary> /// <param name="name">The name.</param> public void EnglishSpeaker(string name) { Console.WriteLine( string.Format("Hello my name is {0} and I am English speaker.\n", name)); } /// <summary> /// the Chineses speaker. /// </summary> public void ChineseSpeaker(string name) { Console.WriteLine( string.Format("您好我的名字叫{0},我是講普通話的。\n", name)); } 好啦現(xiàn)在我們有兩個(gè)方法分別是說普通話和英語,,現(xiàn)在我們的程序會(huì)說普通話和英語啦?,F(xiàn)在我們考慮究竟什么時(shí)候講普通話什么時(shí)候講英語,那不簡單我們加個(gè)判斷就OK啦,,是的我們可以通過switch或者if else就可以實(shí)現(xiàn)啦,。 /// <summary>
/// 根據(jù)上下文調(diào)用不同的方法 /// </summary> /// <param name="name">string</param> /// <param name="lang">enum</param> private static void Say(string name, Language lang) { switch (lang) { case Language.Chinese: Program.ChineseSpeaker(name); break; case Language.English: Program.EnglishSpeaker(name); break; default : break; } } 但假設(shè)我們現(xiàn)在又要增加新的語言西班牙語,同樣我們可以增加西班牙語,,但我們必須修改switch語句增加判斷,,這不符合OOP中的OCP(對擴(kuò)展開放,對修改關(guān)閉原則),這時(shí)候委托該登場 ,。/// <summary>
/// Define speak delegate. /// </summary> /// <param name="name"></param> private delegate void SpeakDelegate(string name); 首先我們定義了一種委托類型SpeakDelegate,,然后我們通過修改Say方法看看該如何使用委托變量 。/// <summary>
/// The base say function. /// </summary> /// <param name="name">The name.</param> /// <param name="speaker">The speaker.</param> private static void Say(string name, SpeakDelegate speaker) { ///Inoke the speaker function. speaker(name); } 現(xiàn)在我們的參數(shù)已經(jīng)不是枚舉類型了,,而是一個(gè)委托類型變量,,而且實(shí)現(xiàn)的具體代碼也沒有了switch語句了,比之前簡單了許多?,F(xiàn)在大家知道如何去調(diào)用Say方法吧!沒錯(cuò)我們只需傳遞一個(gè)name和一個(gè)具體實(shí)現(xiàn)函數(shù)名就OK了,。 ///傳遞函數(shù)名進(jìn)行委托方法綁定
Program.Say("鈞航", ChineseSpeaker); Program.Say("JK.Rush", EnglishSpeaker); 自定義委托相信大家都會(huì)了,接下來我將介紹一下.NET中委托實(shí)現(xiàn),由于許多使用委托的例子都是事件,,所以下面的例子也采用事件,。但請大家要注意“可以使用委托,但卻沒有定義事件”的情況(例如:回調(diào)函數(shù)),。 4 .NET中的事件委托 舉一個(gè)簡單的例子,,.NET中經(jīng)常使用的控件Button,當(dāng)我們把Button 控件 drap and drop到界面,,然后雙擊界面的Button我們發(fā)現(xiàn)程序中自動(dòng)生成了一個(gè)響應(yīng)Button的事件方法,,然后我們給事件方法添加Code之后,當(dāng)我們點(diǎn)擊該Button就響應(yīng)該方法了,,但我們沒有看到代碼中有任何的委托和事件之類的定義,,其實(shí)這些.NET都已經(jīng)做好了。我們可以查看如下文件,。
圖2事件委托實(shí)現(xiàn) 如上圖所示我們打開Designer文件,,事件委托的實(shí)現(xiàn)都在這里實(shí)現(xiàn)了。 其中,,EventHandler就是一個(gè)代理類型,,可以認(rèn)為它是一個(gè)“類”,是所有返回類型為void,,具備兩個(gè)參數(shù)分別是object sender和EventArgs e,,第一個(gè)參數(shù)表示引發(fā)事件的控件,或者說它表示點(diǎn)擊的那個(gè)按鈕,。通過以下的代碼我們細(xì)細(xì)解析一下。
private void button1_Click(object sender, EventArgs e)
{ //獲取被點(diǎn)擊Button的實(shí)例 Button objBotton = sender as Button; if (objBotton != null) { objBotton.Text = "Hello you click me."; objBotton.AutoSize = true; } else { //Exception Handle. }
圖3點(diǎn)擊產(chǎn)生效果 OK現(xiàn)在明白了sender就是傳遞一個(gè)被點(diǎn)擊對象的實(shí)例,,第二個(gè)參數(shù)名叫e的EventArgs參數(shù),,用于 表示附加的事件關(guān)聯(lián)的事件信息。當(dāng)點(diǎn)擊按鈕時(shí),,沒有附加任何關(guān)聯(lián)的事件信息,,如上的點(diǎn)擊事件,第二參數(shù)并不表示任何有用的信息,。但什么時(shí)候會(huì)用到呢? 我們先介紹一下EventArgs這個(gè)的類型,。其實(shí)這個(gè)類并沒有太多的功能,它主要是作為一個(gè)基類讓其他類去實(shí)現(xiàn)具體的功能和定義,,當(dāng)我們搜索EventArgs發(fā)現(xiàn)很多類是繼承于它的,。 public class EventArgs
{
// Fields
public static readonly EventArgs Empty;
// Methods static EventArgs();
public EventArgs();}
舉其中的ImageClickEventArgs為例,它繼承于EventArgs,,而且還添加了自己的字段用來基類X和Y的坐標(biāo)值(這是一個(gè)ImageButton被點(diǎn)擊時(shí)候響應(yīng)的),,然后獲取該按鈕的X和Y坐標(biāo)。
圖4獲取ImageClickEventArgs關(guān)聯(lián)點(diǎn)擊坐標(biāo) 前面提到其他事件關(guān)聯(lián)信息類型都是通過繼承EventArgs實(shí)現(xiàn)的,所以說我們自己也可以自定義一個(gè)事件關(guān)聯(lián)信息類型,,如下我們只需繼承EventArgs就OK了,。 /// <summary>
/// 自定義事件關(guān)聯(lián)類 /// </summary> public class ColorChangedEventArgs : EventArgs { private Color color; /// <summary> /// Initializes a new instance of the <see cref="ColorChangedEventArgs"/> class. /// </summary> /// <param name="c">The c.</param> public ColorChangedEventArgs(Color c) { color = c; } /// <summary> /// Gets the color of the get. /// </summary> /// <value> /// The color of the get. /// </value> public Color GetColor { get { return color; } } } 5.自定義事件委托 多播委托 前面使用的每個(gè)委托都只包含一個(gè)方法調(diào)用。調(diào)用一個(gè)委托就調(diào)用一個(gè)方法調(diào)用,。如果要通過一個(gè)委托調(diào)用多個(gè)方法,,那就需要使用委托的多播特性。如果調(diào)用多播委托,,就可以按委托添加次序連續(xù)調(diào)用多個(gè)方法,。為此,委托的簽名就必須返回void;否則,,就只能得到委托調(diào)用的最后一個(gè)方法的結(jié)果,,接下來看看多播實(shí)現(xiàn)。
namespace Multi_Delegate
{ delegate void StringProcessor(); public class Person { private string _Name; public Person(string name) { this._Name = name; } public void Say() { Console.WriteLine("Hello my name is {0}, what's your name.\n", this._Name); } public void Reply() { Console.WriteLine("Hello my name is {0} and nice to meet you.\n", this._Name); } } class Program { static void Main(string[] args) { Person Jack = new Person("Jack"); Person Oliver = new Person("Oliver"); StringProcessor sp = null; //綁定多播方法調(diào)用 sp += Jack.Say; sp += Oliver.Reply; sp(); Console.ReadKey(); } } } 也許有人覺得很簡單,,實(shí)現(xiàn)的確簡單明了,,就是通過“+”把方法調(diào)用綁定到委托變量中,如果我們用“-”就可以移除綁定到委托變量方法了,。 事件 前面一直沒有解釋什么是事件,,現(xiàn)在讓我用一句話解釋事件和委托的關(guān)系吧! 事件和委托關(guān)系就像是屬性和字段的關(guān)系,為了剛好的實(shí)現(xiàn)OOP的編程原則,,事件對委托進(jìn)行了封裝,。 現(xiàn)在我們修改前面的代碼,使用事件對委托進(jìn)行封裝,。 /// 使用事件對委托進(jìn)行封裝
/// </summary> public class Say { /// <summary> /// 封裝委托字段 /// </summary> public static event SpeakDelegate speakDelegate; /// <summary> /// 調(diào)用委托具體實(shí)現(xiàn)方法 /// </summary> /// <param name="name"></param> public static void SayManager(string name) { speakDelegate(name); } } /// <summary> /// 客戶端調(diào)用委托 /// </summary> /// <param name="args"></param> static void Main(string[] args) { Say.speakDelegate += Program.ChineseSpeaker; Say.speakDelegate += Program.EnglishSpeaker; Say.SayManager("Jackson"); Console.ReadKey();
圖5自定義委托 現(xiàn)在讓我們看看編譯后Say類就可以充分證明我們的結(jié)論:事件是對委托封裝,。
圖6自定義事件編譯后的代碼 大家看到在編譯后的代碼中出現(xiàn)了一個(gè)私有的委托變量,然后接下是一個(gè)公用事件委托變量,,這進(jìn)一步說明了事件是對委托的封裝,。
圖7自定義事件編譯后MSIL代碼 6.事件委托實(shí)現(xiàn)觀察者模式 前面我們介紹按鈕事件響應(yīng)是從發(fā)送者和接收者的角度出發(fā)的,現(xiàn)在我們以設(shè)計(jì)模式中的觀察者模式為例,。
圖8GoF觀察者架構(gòu) /// 使用事件對委托進(jìn)行封裝
/// </summary> public class Say { /// <summary> /// 封裝委托字段 /// </summary> public static event SpeakDelegate speakDelegate; /// <summary> /// 調(diào)用委托具體實(shí)現(xiàn)方法 /// </summary> /// <param name="name"></param> public static void SayManager(string name) { speakDelegate(name); } } /// <summary> /// 客戶端調(diào)用委托 /// </summary> /// <param name="args"></param> static void Main(string[] args) { Say.speakDelegate += Program.ChineseSpeaker; Say.speakDelegate += Program.EnglishSpeaker; Say.SayManager("Jackson"); Console.ReadKey(); |
|