国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

C# 中的委托和事件(詳解)

2019-11-08 02:06:38
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

C# 中的委托和事件

       委托和事件在 .NET Framework 中的應(yīng)用非常廣泛,然而,較好地理解委托和事件對(duì)很多接觸 C# 時(shí)間不長(zhǎng)的人來(lái)說(shuō)并不容易。它們就像是一道檻兒,過(guò)了這個(gè)檻的人,覺(jué)得真是太容易了,而沒(méi)有過(guò)去的人每次見(jiàn)到委托和事件就覺(jué)得心里堵得慌,渾身不自在。本章中,我將由淺入深地講述什么是委托、為什么要使用委托、事件的由來(lái)、.NET Framework 中的委托和事件、委托中方法異常和超時(shí)的處理、委托與異步編程、委托和事件對(duì)Observer 設(shè)計(jì)模式的意義,對(duì)它們的編譯代碼也做了討論。

1.1 理解委托

1.1.1 將方法作為方法的參數(shù)

我們先不管這個(gè)標(biāo)題如何的繞口,也不管委托究竟是個(gè)什么東西,來(lái)看下面這兩個(gè)最簡(jiǎn)單的方法,它們不過(guò)是在屏幕上輸出一句問(wèn)候的話語(yǔ):

public void GreetPeople(string name){    EnglishGreeting(name);} public void EnglishGreeting(string name){    Console.WriteLine("Good Morning, " + name);}

暫且不管這兩個(gè)方法有沒(méi)有什么實(shí)際意義。GreetPeople 用于向某人問(wèn)好,當(dāng)我們傳遞代表某人姓名的 name 參數(shù),比如說(shuō)“Liker”進(jìn)去的時(shí)候,在這個(gè)方法中,將調(diào)用 EnglishGreeting 方法,再次傳遞 name 參數(shù),EnglishGreeting 則用于向屏幕輸出 “Good Morning, Liker”。

現(xiàn)在假設(shè)這個(gè)程序需要進(jìn)行全球化,哎呀,不好了,我是中國(guó)人,我不明白“Good Morning”是什么意思,怎么辦呢?好吧,我們?cè)偌觽€(gè)中文版的問(wèn)候方法:

public void ChineseGreeting(string name){    Console.WriteLine("早上好, " + name);}這時(shí)候,GreetPeople 也需要改一改了,不然如何判斷到底用哪個(gè)版本的 Greeting 問(wèn)候方法合適呢?在進(jìn)行這個(gè)之前,我們最好再定義一個(gè)枚舉作為判斷的依據(jù):

public enum Language{    English, Chinese} public void GreetPeople(string name, Language lang){    switch (lang)    {        case Language.English:            EnglishGreeting(name);            break;        case Language.Chinese:            ChineseGreeting(name);            break;    }}

OK,盡管這樣解決了問(wèn)題,但我不說(shuō)大家也很容易想到,這個(gè)解決方案的可擴(kuò)展性很差,如果日后我們需要再添加韓文版、日文版,就不得不反復(fù)修改枚舉和GreetPeople() 方法,以適應(yīng)新的需求。

在考慮新的解決方案之前,我們先看看 GreetPeople 的方法簽名:

       public void GreetPeople(string name, Language lang);

我們僅看 string name,在這里,string 是參數(shù)類型,name 是參數(shù)變量,當(dāng)我們賦給 name 字符串“Liker”時(shí),它就代表“Liker”這個(gè)值;當(dāng)我們賦給它“李志中”時(shí),它又代表著“李志中”這個(gè)值。然后,我們可以在方法體內(nèi)對(duì)這個(gè) name 進(jìn)行其他操作。哎,這簡(jiǎn)直是廢話么,剛學(xué)程序就知道了。

如果你再仔細(xì)想想,假如 GreetPeople() 方法可以接受一個(gè)參數(shù)變量,這個(gè)變量可以代表另一個(gè)方法,當(dāng)我們給這個(gè)變量賦值 EnglishGreeting 的時(shí)候,它代表著 EnglsihGreeting() 這個(gè)方法;當(dāng)我們給它賦值ChineseGreeting 的時(shí)候,它又代表著 ChineseGreeting() 法。我們將這個(gè)參數(shù)變量命名為 MakeGreeting,那么不是可以如同給 name 賦值時(shí)一樣,在調(diào)用 GreetPeople()方法的時(shí)候,給這個(gè)MakeGreeting 參數(shù)也賦上值么(ChineseGreeting 或者EnglsihGreeting 等)?然后,我們?cè)诜椒w內(nèi),也可以像使用別的參數(shù)一樣使用MakeGreeting。但是,由于 MakeGreeting 代表著一個(gè)方法,它的使用方式應(yīng)該和它被賦的方法(比如ChineseGreeting)是一樣的,比如:MakeGreeting(name);

好了,有了思路了,我們現(xiàn)在就來(lái)改改GreetPeople()方法,那么它應(yīng)該是這個(gè)樣子了:

public void GreetPeople(string name, *** MakeGreeting)

{

       MakeGreeting(name);

}

注意到 *** ,這個(gè)位置通常放置的應(yīng)該是參數(shù)的類型,但到目前為止,我們僅僅是想到應(yīng)該有個(gè)可以代表方法的參數(shù),并按這個(gè)思路去改寫 GreetPeople 方法,現(xiàn)在就出現(xiàn)了一個(gè)大問(wèn)題:這個(gè)代表著方法的 MakeGreeting 參數(shù)應(yīng)該是什么類型的?

說(shuō)明:這里已不再需要枚舉了,因?yàn)樵诮oMakeGreeting 賦值的時(shí)候動(dòng)態(tài)地決定使用哪個(gè)方法,是 ChineseGreeting 還是 EnglishGreeting,而在這個(gè)兩個(gè)方法內(nèi)部,已經(jīng)對(duì)使用“Good Morning”還是“早上好”作了區(qū)分。

聰明的你應(yīng)該已經(jīng)想到了,現(xiàn)在是委托該出場(chǎng)的時(shí)候了,但講述委托之前,我們?cè)倏纯碝akeGreeting 參數(shù)所能代表的 ChineseGreeting()和EnglishGreeting()方法的簽名:

public void EnglishGreeting(string name)

public void ChineseGreeting(string name)

如同 name 可以接受 String 類型的“true”和“1”,但不能接受bool 類型的true 和int 類型的1 一樣。MakeGreeting 的參數(shù)類型定義應(yīng)該能夠確定 MakeGreeting 可以代表的方法種類,再進(jìn)一步講,就是 MakeGreeting 可以代表的方法的參數(shù)類型和返回類型。

于是,委托出現(xiàn)了:它定義了 MakeGreeting 參數(shù)所能代表的方法的種類,也就是 MakeGreeting 參數(shù)的類型。

本例中委托的定義:

    public delegate void GreetingDelegate(string name);

與上面 EnglishGreeting() 方法的簽名對(duì)比一下,除了加入了delegate 關(guān)鍵字以外,其余的是不是完全一樣?現(xiàn)在,讓我們?cè)俅胃膭?dòng)GreetPeople()方法,如下所示:

public delegate void GreetingDelegate(string name);public void GreetPeople(string name, GreetingDelegate MakeGreeting){    MakeGreeting(name);}如你所見(jiàn),委托 GreetingDelegate 出現(xiàn)的位置與 string 相同,string 是一個(gè)類型,那么 GreetingDelegate 應(yīng)該也是一個(gè)類型,或者叫類(Class)。但是委托的聲明方式和類卻完全不同,這是怎么一回事?實(shí)際上,委托在編譯的時(shí)候確實(shí)會(huì)編譯成類。因?yàn)?Delegate 是一個(gè)類,所以在任何可以聲明類的地方都可以聲明委托。更多的內(nèi)容將在下面講述,現(xiàn)在,請(qǐng)看看這個(gè)范例的完整代碼:

public delegate void GreetingDelegate(string name); class PRogram{    private static void EnglishGreeting(string name)    {        Console.WriteLine("Good Morning, " + name);    }     private static void ChineseGreeting(string name)    {        Console.WriteLine("早上好, " + name);    }     private static void GreetPeople(string name, GreetingDelegate MakeGreeting)    {        MakeGreeting(name);    }     static void Main(string[] args)    {        GreetPeople("Liker", EnglishGreeting);        GreetPeople("李志中", ChineseGreeting);        Console.ReadLine();    }}

我們現(xiàn)在對(duì)委托做一個(gè)總結(jié):委托是一個(gè)類,它定義了方法的類型,使得可以將方法當(dāng)作另一個(gè)方法的參數(shù)來(lái)進(jìn)行傳遞,這種將方法動(dòng)態(tài)地賦給參數(shù)的做法,可以避免在程序中大量使用If … Else(Switch)語(yǔ)句,同時(shí)使得程序具有更好的可擴(kuò)展性。

1.1.2 將方法綁定到委托

看到這里,是不是有那么點(diǎn)如夢(mèng)初醒的感覺(jué)?于是,你是不是在想:在上面的例子中,我不一定要直接在 GreetPeople() 方法中給 name 參數(shù)賦值,我可以像這樣使用變量:

static void Main(string[] args){    GreetPeople("Liker", EnglishGreeting);    GreetPeople("李志中", ChineseGreeting);    Console.ReadLine();}而既然委托 GreetingDelegate 和類型 string 的地位一樣,都是定義了一種參數(shù)類型,那么,我是不是也可以這么使用委托?

static void Main(string[] args){    GreetingDelegate delegate1, delegate2;    delegate1 = EnglishGreeting;    delegate2 = ChineseGreeting;    GreetPeople("Liker", delegate1);    GreetPeople("李志中", delegate2);    Console.ReadLine();}如你所料,這樣是沒(méi)有問(wèn)題的,程序一如預(yù)料的那樣輸出。這里,我想說(shuō)的是委托不同于 string 的一個(gè)特性:可以將多個(gè)方法賦給同一個(gè)委托,或者叫將多個(gè)方法綁定到同一個(gè)委托,當(dāng)調(diào)用這個(gè)委托的時(shí)候,將依次調(diào)用其所綁定的方法。在這個(gè)例子中,語(yǔ)法如下:

static void Main(string[] args){    GreetingDelegate delegate1;    delegate1 = EnglishGreeting;     delegate1 += ChineseGreeting;    GreetPeople("Liker", delegate1);    Console.ReadLine();}實(shí)際上,我們可以也可以繞過(guò)GreetPeople 方法,通過(guò)委托來(lái)直接調(diào)用EnglishGreeting 和ChineseGreeting:

static void Main(string[] args){    GreetingDelegate delegate1;    delegate1 = EnglishGreeting;    delegate1 += ChineseGreeting;     delegate1("Liker");    Console.ReadLine();}

說(shuō)明:這在本例中是沒(méi)有問(wèn)題的,但回頭看下上面 GreetPeople() 的定義,在它之中可以做一些對(duì)于 EnglshihGreeting 和 ChineseGreeting 來(lái)說(shuō)都需要進(jìn)行的工作,為了簡(jiǎn)便我做了省略。

注意這里,第一次用的“=”,是賦值的語(yǔ)法;第二次,用的是“+=”,是綁定的語(yǔ)法。如果第一次就使用“+=”,將出現(xiàn)“使用了未賦值的局部變量”的編譯錯(cuò)誤。我們也可以使用下面的代碼來(lái)這樣簡(jiǎn)化這一過(guò)程:

GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);delegate1 += ChineseGreeting;既然給委托可以綁定一個(gè)方法,那么也應(yīng)該有辦法取消對(duì)方法的綁定,很容易想到,這個(gè)語(yǔ)法是“-=”:

static void Main(string[] args){    GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);    delegate1 += ChineseGreeting;    GreetPeople("Liker", delegate1);    Console.WriteLine();        delegate1 -= EnglishGreeting;    GreetPeople("李志中", delegate1);    Console.ReadLine();}

讓我們?cè)俅螌?duì)委托作個(gè)總結(jié):

    使用委托可以將多個(gè)方法綁定到同一個(gè)委托變量,當(dāng)調(diào)用此變量時(shí)(這里用“調(diào)用”這個(gè)詞,是因?yàn)榇俗兞看硪粋€(gè)方法),可以依次調(diào)用所有綁定的方法。

 

1.2 事件的由來(lái)

1.2.1 更好的封裝性

我們繼續(xù)思考上面的程序:上面的三個(gè)方法都定義在 Programe 類中,這樣做是為了理解的方便,實(shí)際應(yīng)用中,通常都是 GreetPeople 在一個(gè)類中,ChineseGreeting 和 EnglishGreeting 在另外的類中。現(xiàn)在你已經(jīng)對(duì)委托有了初步了解,是時(shí)候?qū)ι厦娴睦幼鰝€(gè)改進(jìn)了。假設(shè)我們將 GreetingPeople() 放在一個(gè)叫 GreetingManager 的類中,那么新程序應(yīng)該是這個(gè)樣子的:

namespace Delegate{    public delegate void GreetingDelegate(string name);      public class GreetingManager    {        public void GreetPeople(string name, GreetingDelegate MakeGreeting)        {            MakeGreeting(name);        }    }     class Program    {        private static void EnglishGreeting(string name)        {            Console.WriteLine("Good Morning, " + name);        }         private static void ChineseGreeting(string name)        {            Console.WriteLine("早上好, " + name);        }         static void Main(string[] args)        {            GreetingManager gm = new GreetingManager();            gm.GreetPeople("Liker", EnglishGreeting);            gm.GreetPeople("李志中", ChineseGreeting);        }    }}

我們運(yùn)行這段代碼,嗯,沒(méi)有任何問(wèn)題。程序一如預(yù)料地那樣輸出了:

// ************************************************************************

Good Morning, Liker

早上好, 李志中

// ************************************************************************

 

現(xiàn)在,假設(shè)我們需要使用上一節(jié)學(xué)到的知識(shí),將多個(gè)方法綁定到同一個(gè)委托變量,該如何做呢?讓我們?cè)俅胃膶懘a:

static void Main(string[] args){    GreetingManager gm = new GreetingManager();    GreetingDelegate delegate1;    delegate1 = EnglishGreeting;    delegate1 += ChineseGreeting;    gm.GreetPeople("Liker", delegate1);}

輸出:

Good Morning, Liker

早上好, Liker

 

到了這里,我們不禁想到:面向?qū)ο笤O(shè)計(jì),講究的是對(duì)象的封裝,既然可以聲明委托類型的變量(在上例中是delegate1),我們何不將這個(gè)變量封裝到 GreetManager 類中?在這個(gè)類的客戶端中使用不是更方便么?于是,我們改寫GreetManager 類,像這樣:

public class GreetingManager{    /// <summary>    /// 在 GreetingManager 類的內(nèi)部聲明 delegate1 變量    /// </summary>    public GreetingDelegate delegate1;     public void GreetPeople(string name, GreetingDelegate MakeGreeting)    {        MakeGreeting(name);    }}現(xiàn)在,我們可以這樣使用這個(gè)委托變量:
static void Main(string[] args){    GreetingManager gm = new GreetingManager();    gm.delegate1 = EnglishGreeting;    gm.delegate1 += ChineseGreeting;    gm.GreetPeople("Liker", gm.delegate1);}

輸出為:

Good Morning, Liker

早上好, Liker

盡管這樣做沒(méi)有任何問(wèn)題,但我們發(fā)現(xiàn)這條語(yǔ)句很奇怪。在調(diào)用gm.GreetPeople 方法的時(shí)候,再次傳遞了gm 的delegate1 字段,既然如此,我們何不修改 GreetingManager 類成這樣:

public class GreetingManager {     /// <summary>     /// 在 GreetingManager 類的內(nèi)部聲明 delegate1 變量     /// </summary>     public GreetingDelegate delegate1;      public void GreetPeople(string name)     {         if (delegate1 != null) // 如果有方法注冊(cè)委托變量         {              delegate1(name); // 通過(guò)委托調(diào)用方法         }     } }在客戶端,調(diào)用看上去更簡(jiǎn)潔一些:
static void Main(string[] args){    GreetingManager gm = new GreetingManager();    gm.delegate1 = EnglishGreeting;    gm.delegate1 += ChineseGreeting;    gm.GreetPeople("Liker"); //注意,這次不需要再傳遞 delegate1 變量}

盡管這樣達(dá)到了我們要的效果,但是還是存在著問(wèn)題:在這里,delegate1 和我們平時(shí)用的string 類型的變量沒(méi)有什么分別,而我們知道,并不是所有的字段都應(yīng)該聲明成public,合適的做法是應(yīng)該public 的時(shí)候public,應(yīng)該private 的時(shí)候private。

我們先看看如果把 delegate1 聲明為 private 會(huì)怎樣?結(jié)果就是:這簡(jiǎn)直就是在搞笑。因?yàn)槁暶魑械哪康木褪菫榱税阉┞对陬惖目蛻舳诉M(jìn)行方法的注冊(cè),你把它聲明為 private 了,客戶端對(duì)它根本就不可見(jiàn),那它還有什么用?

再看看把delegate1 聲明為 public 會(huì)怎樣?結(jié)果就是:在客戶端可以對(duì)它進(jìn)行隨意的賦值等操作,嚴(yán)重破壞對(duì)象的封裝性。

最后,第一個(gè)方法注冊(cè)用“=”,是賦值語(yǔ)法,因?yàn)橐M(jìn)行實(shí)例化,第二個(gè)方法注冊(cè)則用的是“+=”。但是,不管是賦值還是注冊(cè),都是將方法綁定到委托上,除了調(diào)用時(shí)先后順序不同,再?zèng)]有任何的分別,這樣不是讓人覺(jué)得很別扭么?

現(xiàn)在我們想想,如果delegate1 不是一個(gè)委托類型,而是一個(gè)string 類型,你會(huì)怎么做?答案是使用屬性對(duì)字段進(jìn)行封裝。

于是,Event 出場(chǎng)了,它封裝了委托類型的變量,使得:在類的內(nèi)部,不管你聲明它是public還是protected,它總是private 的。在類的外部,注冊(cè)“+=”和注銷“-=”的訪問(wèn)限定符與你在聲明事件時(shí)使用的訪問(wèn)符相同。我們改寫GreetingManager 類,它變成了這個(gè)樣子:

public class GreetingManager{    //這一次我們?cè)谶@里聲明一個(gè)事件    public event GreetingDelegate MakeGreet;     public void GreetPeople(string name)    {        MakeGreet(name);    }}很容易注意到:MakeGreet 事件的聲明與之前委托變量 delegate1 的聲明唯一的區(qū)別是多了一個(gè) event 關(guān)鍵字。看到這里,在結(jié)合上面的講解,你應(yīng)該明白到:事件其實(shí)沒(méi)什么不好理解的,聲明一個(gè)事件不過(guò)類似于聲明一個(gè)進(jìn)行了封裝的委托類型的變量而已

為了證明上面的推論,如果我們像下面這樣改寫Main 方法:

static void Main(string[] args){    GreetingManager gm = new GreetingManager();    gm.MakeGreet = EnglishGreeting; // 編譯錯(cuò)誤1    gm.MakeGreet += ChineseGreeting;    gm.GreetPeople("Liker");}會(huì)得到編譯錯(cuò)誤:

1.2.2 限制類型能力

使用事件不僅能獲得比委托更好的封裝性以外,還能限制含有事件的類型的能力。這是什么意思呢?它的意思是說(shuō):事件應(yīng)該由事件發(fā)布者觸發(fā),而不應(yīng)該由事件的客戶端(客戶程序)來(lái)觸發(fā)。請(qǐng)看下面的范例:

using System; class Program{    static void Main(string[] args)    {        Publishser pub = new Publishser();        Subscriber sub = new Subscriber();        pub.NumberChanged += new NumberChangedEventHandler(sub.OnNumberChanged);        pub.DoSomething(); // 應(yīng)該通過(guò)DoSomething()來(lái)觸發(fā)事件        pub.NumberChanged(100); // 但可以被這樣直接調(diào)用,對(duì)委托變量的不恰當(dāng)使用    }} /// <summary>/// 定義委托/// </summary>/// <param name="count"></param>public delegate void NumberChangedEventHandler(int count); /// <summary>/// 定義事件發(fā)布者/// </summary>public class Publishser{    private int count;     public NumberChangedEventHandler NumberChanged; // 聲明委托變量     //public event NumberChangedEventHandler NumberChanged; // 聲明一個(gè)事件     public void DoSomething()    {        // 在這里完成一些工作 ...         if (NumberChanged != null) // 觸發(fā)事件        {             count++;            NumberChanged(count);        }    }} /// <summary>/// 定義事件訂閱者/// </summary>public class Subscriber{    public void OnNumberChanged(int count)    {        Console.WriteLine("Subscriber notified: count = {0}", count);    }}上面代碼定義了一個(gè)NumberChangedEventHandler 委托,然后我們創(chuàng)建了事件的發(fā)布者Publisher 和訂閱者Subscriber。當(dāng)使用委托變量時(shí),客戶端可以直接通過(guò)委托變量觸發(fā)事件,也就是直接調(diào)用pub.NumberChanged(100),這將會(huì)影響到所有注冊(cè)了該委托的訂閱者。而事件的本意應(yīng)該為在事件發(fā)布者在其本身的某個(gè)行為中觸發(fā),比如說(shuō)在方法DoSomething()中滿足某個(gè)條件后觸發(fā)。通過(guò)添加event 關(guān)鍵字來(lái)發(fā)布事件,事件發(fā)布者的封裝性會(huì)更好,事件僅僅是供其他類型訂閱,而客戶端不能直接觸發(fā)事件(語(yǔ)句pub.NumberChanged(100)無(wú)法通過(guò)編譯),事件只能在事件發(fā)布者Publisher 類的內(nèi)部觸發(fā)(比如在方法pub.DoSomething()中),換言之,就是NumberChanged(100)語(yǔ)句只能在Publisher 內(nèi)部被調(diào)用。大家可以嘗試一下,將委托變量的聲明那行代碼注釋掉,然后取消下面事件聲明的注釋。此時(shí)程序是無(wú)法編譯的,當(dāng)你使用了event 關(guān)鍵字之后,直接在客戶端觸發(fā)事件這種行為,也就是直接調(diào)用pub.NumberChanged(100),是被禁止的。事件只能通過(guò)調(diào)用DoSomething() 來(lái)觸發(fā)。這樣才是事件的本意,事件發(fā)布者的封裝才會(huì)更好。

就好像如果我們要定義一個(gè)數(shù)字類型,我們會(huì)使用int 而不是使用object 一樣,給予對(duì)象過(guò)多的能力并不見(jiàn)得是一件好事,應(yīng)該是越合適越好。盡管直接使用委托變量通常不會(huì)有什么問(wèn)題,但它給了客戶端不應(yīng)具有的能力,而使用事件,可以限制這一能力,更精確地對(duì)類型進(jìn)行封裝

說(shuō) 明:這里還有一個(gè)約定俗稱的規(guī)定,就是訂閱事件的方法的命名,通常為“On 事件名”,比如這里的OnNumberChanged

 

1.3 委托的編譯代碼

這時(shí)候,我們注釋掉編譯錯(cuò)誤的行,然后重新進(jìn)行編譯,再借助 Reflactor 來(lái)對(duì) event 的聲明語(yǔ)句做一探究,看看為什么會(huì)發(fā)生這樣的錯(cuò)誤:

clip_image002

可以看到,實(shí)際上盡管我們?cè)贕reetingManager 里將 MakeGreet 聲明為public,但是,實(shí)際上MakeGreet 會(huì)被編譯成私有字段,難怪會(huì)發(fā)生上面的編譯錯(cuò)誤了,因?yàn)樗揪筒辉试S在GreetingManager 類的外面以賦值的方式訪問(wèn),從而驗(yàn)證了我們上面所做的推論

我們?cè)龠M(jìn)一步看下MakeGreet 所產(chǎn)生的代碼:

// ************************************************************************

private GreetingDelegate MakeGreet; //對(duì)事件的聲明實(shí)際是聲明一個(gè)私有的委托變量

[MethodImpl(MethodImplOptions.Synchronized)]

public void add_MakeGreet(GreetingDelegate value)

{

this.MakeGreet = (GreetingDelegate) Delegate.Combine(this.MakeGreet, value);

}

[MethodImpl(MethodImplOptions.Synchronized)]

public void remove_MakeGreet(GreetingDelegate value)

{

this.MakeGreet = (GreetingDelegate) Delegate.Remove(this.MakeGreet, value);

}

// ************************************************************************

現(xiàn)在已經(jīng)很明確了:MakeGreet 事件確實(shí)是一個(gè)GreetingDelegate 類型的委托,只不過(guò)不管是不是聲明為public,它總是被聲明為private。另外,它還有兩個(gè)方法,分別是add_MakeGreet和remove_MakeGreet,這兩個(gè)方法分別用于注冊(cè)委托類型的方法和取消注冊(cè)。實(shí)際上也就是:“+= ”對(duì)應(yīng) add_MakeGreet,“-=”對(duì)應(yīng)remove_MakeGreet。而這兩個(gè)方法的訪問(wèn)限制取決于聲明事件時(shí)的訪問(wèn)限制符。

在add_MakeGreet()方法內(nèi)部,實(shí)際上調(diào)用了System.Delegate 的Combine()靜態(tài)方法,這個(gè)方法用于將當(dāng)前的變量添加到委托鏈表中。

我們前面提到過(guò)兩次,說(shuō)委托實(shí)際上是一個(gè)類,在我們定義委托的時(shí)候:

// ************************************************************************

public delegate void GreetingDelegate(string name);

// ************************************************************************

當(dāng)編譯器遇到這段代碼的時(shí)候,會(huì)生成下面這樣一個(gè)完整的類:

// ************************************************************************

public class GreetingDelegate:System.MulticastDelegate

{

public GreetingDelegate(object @object, IntPtr method);

public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object);

public virtual void EndInvoke(IAsyncResult result);

public virtual void Invoke(string name);

}

// ************************************************************************

clip_image004

 

1.4 .NET 框架中的委托和事件

1.4.1 范例說(shuō)明

上面的例子已不足以再進(jìn)行下面的講解了,我們來(lái)看一個(gè)新的范例,因?yàn)橹耙呀?jīng)介紹了很多的內(nèi)容,所以本節(jié)的進(jìn)度會(huì)稍微快一些!

假設(shè)我們有個(gè)高檔的熱水器,我們給它通上電,當(dāng)水溫超過(guò)95 度的時(shí)候:1、揚(yáng)聲器會(huì)開(kāi)始發(fā)出語(yǔ)音,告訴你水的溫度;2、液晶屏也會(huì)改變水溫的顯示,來(lái)提示水已經(jīng)快燒開(kāi)了。

現(xiàn)在我們需要寫個(gè)程序來(lái)模擬這個(gè)燒水的過(guò)程,我們將定義一個(gè)類來(lái)代表熱水器,我們管它叫:Heater,它有代表水溫的字段,叫做 temperature;當(dāng)然,還有必不可少的給水加熱方法 BoilWater(),一個(gè)發(fā)出語(yǔ)音警報(bào)的方法 MakeAlert(),一個(gè)顯示水溫的方法,ShowMsg()。

namespace Delegate{    /// <summary>    /// 熱水器    /// </summary>    public class Heater    {        /// <summary>        /// 水溫        /// </summary>        private int temperature;         /// <summary>        /// 燒水        /// </summary>        public void BoilWater()        {            for (int i = 0; i <= 100; i++)            {                temperature = i;                if (temperature > 95)                {                    MakeAlert(temperature);                    ShowMsg(temperature);                }            }        }                /// <summary>        /// 發(fā)出語(yǔ)音警報(bào)        /// </summary>        /// <param name="param"></param>        private void MakeAlert(int param)        {            Console.WriteLine("Alarm:嘀嘀嘀,水已經(jīng) {0} 度了:", param);        }         /// <summary>        /// 顯示水溫        /// </summary>        /// <param name="param"></param>        private void ShowMsg(int param)        {            Console.WriteLine("Display:水快開(kāi)了,當(dāng)前溫度:{0}度。", param);        }    }     class Program    {        static void Main()        {            Heater ht = new Heater();            ht.BoilWater();        }    }}

1.4.2 Observer 設(shè)計(jì)模式簡(jiǎn)介

上面的例子顯然能完成我們之前描述的工作,但是卻并不夠好。現(xiàn)在假設(shè)熱水器由三部分組成:熱水器、警報(bào)器、顯示器,它們來(lái)自于不同廠商并進(jìn)行了組裝。那么,應(yīng)該是熱水器僅僅負(fù)責(zé)燒水,它不能發(fā)出警報(bào)也不能顯示水溫;在水燒開(kāi)時(shí)由警報(bào)器發(fā)出警報(bào)、顯示器顯示提示和水溫。

這時(shí)候,上面的例子就應(yīng)該變成這個(gè)樣子:

/// <summary>/// 熱水器/// </summary>public class Heater{    private int temperature;         private void BoilWater()    {        for (int i = 0; i <= 100; i++)        {            temperature = i;        }    }} /// <summary>/// 警報(bào)器/// </summary>public class Alarm{    private void MakeAlert(int param)    {        Console.WriteLine("Alarm:嘀嘀嘀,水已經(jīng) {0} 度了:", param);    }} /// <summary>/// 顯示器/// </summary>public class Display{    private void ShowMsg(int param)    {        Console.WriteLine("Display:水已燒開(kāi),當(dāng)前溫度:{0}度。", param);    }}這里就出現(xiàn)了一個(gè)問(wèn)題:如何在水燒開(kāi)的時(shí)候通知報(bào)警器和顯示器?

在繼續(xù)進(jìn)行之前,我們先了解一下Observer 設(shè)計(jì)模式,Observer 設(shè)計(jì)模式中主要包括如下兩類對(duì)象:

Subject:監(jiān)視對(duì)象,它往往包含著其他對(duì)象所感興趣的內(nèi)容。在本范例中,熱水器就是一個(gè)監(jiān)視對(duì)象,它包含的其他對(duì)象所感興趣的內(nèi)容,就是 temprature 字段,當(dāng)這個(gè)字段的值快到100 時(shí),會(huì)不斷把數(shù)據(jù)發(fā)給監(jiān)視它的對(duì)象。

Observer:監(jiān)視者,它監(jiān)視Subject,當(dāng) Subject 中的某件事發(fā)生的時(shí)候,會(huì)告知Observer,而Observer 則會(huì)采取相應(yīng)的行動(dòng)。在本范例中,Observer 有警報(bào)器和顯示器,它們采取的行動(dòng)分別是發(fā)出警報(bào)和顯示水溫。

在本例中,事情發(fā)生的順序應(yīng)該是這樣的:

1. 警報(bào)器和顯示器告訴熱水器,它對(duì)它的溫度比較感興趣(注冊(cè))。

2. 熱水器知道后保留對(duì)警報(bào)器和顯示器的引用。

3. 熱水器進(jìn)行燒水這一動(dòng)作,當(dāng)水溫超過(guò) 95 度時(shí),通過(guò)對(duì)警報(bào)器和顯示器的引用,自動(dòng)調(diào)用警報(bào)器的MakeAlert()方法、顯示器的ShowMsg()方法。

 

類似這樣的例子是很多的,GOF 對(duì)它進(jìn)行了抽象,稱為 Observer 設(shè)計(jì)模式:Observer 設(shè)計(jì)模式是為了定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,以便于當(dāng)一個(gè)對(duì)象的狀態(tài)改變時(shí),其他依賴于它的對(duì)象會(huì)被自動(dòng)告知并更新。Observer 模式是一種松耦合的設(shè)計(jì)模式

 

1.4.3 實(shí)現(xiàn)范例的Observer 設(shè)計(jì)模式

我們之前已經(jīng)對(duì)委托和事件介紹很多了,現(xiàn)在寫代碼應(yīng)該很容易了,現(xiàn)在在這里直接給出代碼,并在注釋中加以說(shuō)明。

namespace Delegate{    public class Heater    {        private int temperature;         public delegate void BoilHandler(int param);         public event BoilHandler BoilEvent;         public void BoilWater()        {            for (int i = 0; i <= 100; i++)            {                temperature = i;                if (temperature > 95)                {                    if (BoilEvent != null)                    {                         BoilEvent(temperature); // 調(diào)用所有注冊(cè)對(duì)象的方法                    }                }            }        }    }     public class Alarm    {        public void MakeAlert(int param)        {            Console.WriteLine("Alarm:嘀嘀嘀,水已經(jīng) {0} 度了:", param);        }    }     public class Display    {        public static void ShowMsg(int param) // 靜態(tài)方法        {             Console.WriteLine("Display:水快燒開(kāi)了,當(dāng)前溫度:{0}度。", param);        }    }     class Program    {        static void Main()        {            Heater heater = new Heater();            Alarm alarm = new Alarm();            heater.BoilEvent += alarm.MakeAlert; // 注冊(cè)方法            heater.BoilEvent += (new Alarm()).MakeAlert; // 給匿名對(duì)象注冊(cè)方法            heater.BoilEvent += Display.ShowMsg; // 注冊(cè)靜態(tài)方法            heater.BoilWater(); // 燒水,會(huì)自動(dòng)調(diào)用注冊(cè)過(guò)對(duì)象的方法        }    }}輸出為:

// ************************************************************************

Alarm:嘀嘀嘀,水已經(jīng) 96 度了:

Alarm:嘀嘀嘀,水已經(jīng) 96 度了:

Display:水快燒開(kāi)了,當(dāng)前溫度:96 度。

// 省略...

// ************************************************************************

 

1.4.4 .NET 框架中的委托與事件

盡管上面的范例很好地完成了我們想要完成的工作,但是我們不僅疑惑:為什么.NET Framework 中的事件模型和上面的不同?為什么有很多的EventArgs 參數(shù)?

在回答上面的問(wèn)題之前,我們先搞懂 .NET Framework 的編碼規(guī)范:

1. 委托類型的名稱都應(yīng)該以 EventHandler 結(jié)束。

2. 委托的原型定義:有一個(gè)void 返回值,并接受兩個(gè)輸入?yún)?shù):一個(gè)Object 類型,一個(gè)EventArgs 類型(或繼承自EventArgs)。

3. 事件的命名為委托去掉 EventHandler 之后剩余的部分。

4. 繼承自 EventArgs 的類型應(yīng)該以EventArgs 結(jié)尾。

再做一下說(shuō)明:

1. 委托聲明原型中的Object 類型的參數(shù)代表了Subject,也就是監(jiān)視對(duì)象,在本例中是Heater(熱水器)。回調(diào)函數(shù)(比如Alarm 的MakeAlert)可以通過(guò)它訪問(wèn)觸發(fā)事件的對(duì)象(Heater)。

2. EventArgs 對(duì)象包含了Observer 所感興趣的數(shù)據(jù),在本例中是temperature。

上面這些其實(shí)不僅僅是為了編碼規(guī)范而已,這樣也使得程序有更大的靈活性。比如說(shuō),如果我們不光想獲得熱水器的溫度,還想在Observer 端(警報(bào)器或者顯示器)方法中獲得它的生產(chǎn)日期、型號(hào)、價(jià)格,那么委托和方法的聲明都會(huì)變得很麻煩,而如果我們將熱水器的引用傳給警報(bào)器的方法,就可以在方法中直接訪問(wèn)熱水器了。

現(xiàn)在我們改寫之前的范例,讓它符合.NET Framework的規(guī)范:

using System;using System.Collections.Generic;using System.Text; namespace Delegate{    public class Heater    {        private int temperature;        public string type = "RealFire 001"; // 添加型號(hào)作為演示        public string area = "China Xian"; // 添加產(chǎn)地作為演示         public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e);         public event BoiledEventHandler Boiled; // 聲明事件         // 定義 BoiledEventArgs 類,傳遞給 Observer 所感興趣的信息        public class BoiledEventArgs : EventArgs        {            public readonly int temperature;            public BoiledEventArgs(int temperature)            {                this.temperature = temperature;            }        }         // 可以供繼承自 Heater 的類重寫,以便繼承類拒絕其他對(duì)象對(duì)它的監(jiān)視        protected virtual void OnBoiled(BoiledEventArgs e)        {            if (Boiled != null)            {                Boiled(this, e); // 調(diào)用所有注冊(cè)對(duì)象的方法            }        }         public void BoilWater()        {            for (int i = 0; i <= 100; i++)            {                temperature = i;                if (temperature > 95)                {                    // 建立BoiledEventArgs 對(duì)象。                     BoiledEventArgs e = new BoiledEventArgs(temperature);                    OnBoiled(e); // 調(diào)用 OnBolied 方法                }            }        }         public class Alarm        {            public void MakeAlert(Object sender, Heater.BoiledEventArgs e)            {                Heater heater = (Heater)sender; // 這里是不是很熟悉呢?                 // 訪問(wèn) sender 中的公共字段                Console.WriteLine("Alarm:{0} - {1}: ", heater.area, heater.type);                Console.WriteLine("Alarm: 嘀嘀嘀,水已經(jīng) {0} 度了:", e.temperature);                Console.WriteLine();            }        }         public class Display        {            public static void ShowMsg(Object sender, Heater.BoiledEventArgs e) // 靜態(tài)方法            {                Heater heater = (Heater)sender;                Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type);                Console.WriteLine("Display:水快燒開(kāi)了,當(dāng)前溫度:{0}度。", e.temperature);                Console.WriteLine();            }        }         class Program        {            static void Main()            {                Heater heater = new Heater();                Alarm alarm = new Alarm();                heater.Boiled += alarm.MakeAlert; //注冊(cè)方法                heater.Boiled += (new Alarm()).MakeAlert; //給匿名對(duì)象注冊(cè)方法                heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); //也可以這么注冊(cè)                heater.Boiled += Display.ShowMsg; //注冊(cè)靜態(tài)方法                heater.BoilWater(); //燒水,會(huì)自動(dòng)調(diào)用注冊(cè)過(guò)對(duì)象的方法            }        }    }}輸出為:

Alarm:China Xian - RealFire 001:

Alarm: 嘀嘀嘀,水已經(jīng) 96 度了:

Alarm:China Xian - RealFire 001:

Alarm: 嘀嘀嘀,水已經(jīng) 96 度了:

Alarm:China Xian - RealFire 001:

Alarm: 嘀嘀嘀,水已經(jīng) 96 度了:

Display:China Xian - RealFire 001:

Display:水快燒開(kāi)了,當(dāng)前溫度:96 度。

// 省略 ...

 

1.5 委托進(jìn)階

1.5.1 為什么委托定義的返回值通常都為 void ?

盡管并非必需,但是我們發(fā)現(xiàn)很多的委托定義返回值都為 void,為什么呢?這是因?yàn)槲凶兞靠梢怨┒鄠€(gè)訂閱者注冊(cè),如果定義了返回值,那么多個(gè)訂閱者的方法都會(huì)向發(fā)布者返回?cái)?shù)值,結(jié)果就是后面一個(gè)返回的方法值將前面的返回值覆蓋掉了,因此,實(shí)際上只能獲得最后一個(gè)方法調(diào)用的返回值。可以運(yùn)行下面的代碼測(cè)試一下。除此以外,發(fā)布者和訂閱者是松耦合的,發(fā)布者根本不關(guān)心誰(shuí)訂閱了它的事件、為什么要訂閱,更別說(shuō)訂閱者的返回值了,所以返回訂閱者的方法返回值大多數(shù)情況下根本沒(méi)有必要。

1.5.2 如何讓事件只允許一個(gè)客戶訂閱?

少數(shù)情況下,比如像上面,為了避免發(fā)生“值覆蓋”的情況(更多是在異步調(diào)用方法時(shí),后面會(huì)討論),我們可能想限制只允許一個(gè)客戶端注冊(cè)。此時(shí)怎么做呢?我們可以向下面這樣,將事件聲明為private 的,然后提供兩個(gè)方法來(lái)進(jìn)行注冊(cè)和取消注冊(cè):

public class Publishser{     private event GeneralEventHandler NumberChanged; // 聲明一個(gè)私有事件     // 注冊(cè)事件    public void Register(GeneralEventHandler method)    {        NumberChanged = method;    }        // 取消注冊(cè)    public void UnRegister(GeneralEventHandler method)    {        NumberChanged -= method;    }     public void DoSomething()    {        // 做某些其余的事情        if (NumberChanged != null)        { // 觸發(fā)事件            string rtn = NumberChanged();            Console.WriteLine("Return: {0}", rtn); // 打印返回的字符串,輸出為Subscriber3        }    }}注意上面,在UnRegister()中,沒(méi)有進(jìn)行任何判斷就使用了NumberChanged -= method 語(yǔ)句。這是因?yàn)榧词筸ethod 方法沒(méi)有進(jìn)行過(guò)注冊(cè),此行語(yǔ)句也不會(huì)有任何問(wèn)題,不會(huì)拋出異常,僅僅是不會(huì)產(chǎn)生任何效果而已。

注意在Register()方法中,我們使用了賦值操作符“=”,而非“+=”,通過(guò)這種方式就避免了多個(gè)方法注冊(cè)。

 

1.7 委托和方法的異步調(diào)用

通常情況下,如果需要異步執(zhí)行一個(gè)耗時(shí)的操作,我們會(huì)新起一個(gè)線程,然后讓這個(gè)線程去執(zhí)行代碼。但是對(duì)于每一個(gè)異步調(diào)用都通過(guò)創(chuàng)建線程來(lái)進(jìn)行操作顯然會(huì)對(duì)性能產(chǎn)生一定的影響,同時(shí)操作也相對(duì)繁瑣一些。.NET 中可以通過(guò)委托進(jìn)行方法的異步調(diào)用,就是說(shuō)客戶端在異步調(diào)用方法時(shí),本身并不會(huì)因?yàn)榉椒ǖ恼{(diào)用而中斷,而是從線程池中抓取一個(gè)線程去執(zhí)行該方法,自身線程(主線程)在完成抓取線程這一過(guò)程之后,繼續(xù)執(zhí)行下面的代碼,這樣就實(shí)現(xiàn)了代碼的并行執(zhí)行。使用線程池的好處就是避免了頻繁進(jìn)行異步調(diào)用時(shí)創(chuàng)建、銷毀線程的開(kāi)銷。當(dāng)我們?cè)谖袑?duì)象上調(diào)用BeginInvoke()時(shí),便進(jìn)行了一個(gè)異步的方法調(diào)用。

事件發(fā)布者和訂閱者之間往往是松耦合的,發(fā)布者通常不需要獲得訂閱者方法執(zhí)行的情況;而當(dāng)使用異步調(diào)用時(shí),更多情況下是為了提升系統(tǒng)的性能,而并非專用于事件的發(fā)布和訂閱這一編程模型。而在這種情況下使用異步編程時(shí),就需要進(jìn)行更多的控制,比如當(dāng)異步執(zhí)行方法的方法結(jié)束時(shí)通知客戶端、返回異步執(zhí)行方法的返回值等。本節(jié)就對(duì) BeginInvoke() 方法、EndInvoke() 方法和其相關(guān)的 IAysncResult 做一個(gè)簡(jiǎn)單的介紹。

我們先看這樣一段代碼,它演示了不使用異步調(diào)用的通常情況:

class Program7{    static void Main(string[] args)    {        Console.WriteLine("Client application started!/n");        Thread.CurrentThread.Name = "Main Thread";        Calculator cal = new Calculator();        int result = cal.Add(2, 5);        Console.WriteLine("Result: {0}/n", result);         // 做某些其它的事情,模擬需要執(zhí)行3 秒鐘        for (int i = 1; i <= 3; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(i));            Console.WriteLine("{0}: Client executed {1} second(s).", Thread.CurrentThread.Name, i);        }         Console.WriteLine("/nPress any key to exit...");        Console.ReadLine();    }} public class Calculator{    public int Add(int x, int y)    {        if (Thread.CurrentThread.IsThreadPoolThread)        {            Thread.CurrentThread.Name = "Pool Thread";        }         Console.WriteLine("Method invoked!");         // 執(zhí)行某些事情,模擬需要執(zhí)行2 秒鐘        for (int i = 1; i <= 2; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(i));            Console.WriteLine("{0}: Add executed {1} second(s).", Thread.CurrentThread.Name, i);        }         Console.WriteLine("Method complete!");        return x + y;    }}

上面代碼有幾個(gè)關(guān)于對(duì)于線程的操作,如果不了解可以看一下下面的說(shuō)明,如果你已經(jīng)了解可以直接跳過(guò):

1. Thread.Sleep(),它會(huì)讓執(zhí)行當(dāng)前代碼的線程暫停一段時(shí)間(如果你對(duì)線程的概念比較陌生,可以理解為使程序的執(zhí)行暫停一段時(shí)間),以毫秒為單位,比如Thread.Sleep(1000),將會(huì)使線程暫停1 秒鐘。在上面我使用了它的重載方法,個(gè)人覺(jué)得使用TimeSpan.FromSeconds(1),可讀性更好一些。

2. Thread.CurrentThread.Name,通過(guò)這個(gè)屬性可以設(shè)置、獲取執(zhí)行當(dāng)前代碼的線程的名稱,值得注意的是這個(gè)屬性只可以設(shè)置一次,如果設(shè)置兩次,會(huì)拋出異常

3. Thread.IsThreadPoolThread,可以判斷執(zhí)行當(dāng)前代碼的線程是否為線程池中的線程

通過(guò)這幾個(gè)方法和屬性,有助于我們更好地調(diào)試異步調(diào)用方法。上面代碼中除了加入了一些對(duì)線程的操作以外再?zèng)]有什么特別之處。我們建了一個(gè)Calculator 類,它只有一個(gè)Add 方法,我們模擬了這個(gè)方法需要執(zhí)行2 秒鐘時(shí)間,并且每隔一秒進(jìn)行一次輸出。而在客戶端程序中,我們使用result 變量保存了方法的返回值并進(jìn)行了打印。隨后,我們?cè)俅文M了客戶端程序接下來(lái)的操作需要執(zhí)行2 秒鐘時(shí)間。運(yùn)行這段程序,會(huì)產(chǎn)生下面的輸出:

// ************************************************************************

Client application started!

Method invoked!

Main Thread: Add executed 1 second(s).

Main Thread: Add executed 2 second(s).

Method complete!

Result: 7

Main Thread: Client executed 1 second(s).

Main Thread: Client executed 2 second(s).

Main Thread: Client executed 3 second(s).

Press any key to exit...

// ************************************************************************

如果你確實(shí)執(zhí)行了這段代碼,會(huì)看到這些輸出并不是一瞬間輸出的,而是執(zhí)行了大概5 秒鐘的時(shí)間,因?yàn)榫€程是串行執(zhí)行的,所以在執(zhí)行完 Add() 方法之后才會(huì)繼續(xù)客戶端剩下的代碼。

接下來(lái)我們定義一個(gè)AddDelegate 委托,并使用BeginInvoke()方法來(lái)異步地調(diào)用它。在上面已經(jīng)介紹過(guò),BeginInvoke()除了最后兩個(gè)參數(shù)為AsyncCallback 類型和Object 類型以外,前面的參數(shù)類型和個(gè)數(shù)與委托定義相同。另外BeginInvoke()方法返回了一個(gè)實(shí)現(xiàn)了IAsyncResult 接口的對(duì)象(實(shí)際上就是一個(gè)AsyncResult 類型實(shí)例,注意這里IAsyncResult 和AysncResult 是不同的,它們均包含在.NET Framework 中)。

AsyncResult 的用途有這么幾個(gè):傳遞參數(shù),它包含了對(duì)調(diào)用了BeginInvoke()的委托的引用;它還包含了BeginInvoke()的最后一個(gè)Object 類型的參數(shù);它可以鑒別出是哪個(gè)方法的哪一次調(diào)用,因?yàn)橥ㄟ^(guò)同一個(gè)委托變量可以對(duì)同一個(gè)方法調(diào)用多次。

EndInvoke()方法接受IAsyncResult 類型的對(duì)象(以及ref 和out 類型參數(shù),這里不討論了,對(duì)它們的處理和返回值類似),所以在調(diào)用BeginInvoke()之后,我們需要保留IAsyncResult,以便在調(diào)用EndInvoke()時(shí)進(jìn)行傳遞。這里最重要的就是EndInvoke()方法的返回值,它就是方法的返回值。除此以外,當(dāng)客戶端調(diào)用EndInvoke()時(shí),如果異步調(diào)用的方法沒(méi)有執(zhí)行完畢,則會(huì)中斷當(dāng)前線程而去等待該方法,只有當(dāng)異步方法執(zhí)行完畢后才會(huì)繼續(xù)執(zhí)行后面的代碼。所以在調(diào)用完BeginInvoke()后立即執(zhí)行EndInvoke()是沒(méi)有任何意義的。我們通常在盡可能早的時(shí)候調(diào)用BeginInvoke(),然后在需要方法的返回值的時(shí)候再去調(diào)用EndInvoke(),或者是根據(jù)情況在晚些時(shí)候調(diào)用。說(shuō)了這么多,我們現(xiàn)在看一下使用異步調(diào)用改寫后上面的代碼吧:

using System.Threading;using System; public delegate int AddDelegate(int x, int y);class Program8{    static void Main(string[] args)    {        Console.WriteLine("Client application started!/n");        Thread.CurrentThread.Name = "Main Thread";        Calculator cal = new Calculator();        AddDelegate del = new AddDelegate(cal.Add);        IAsyncResult asyncResult = del.BeginInvoke(2, 5, null, null); // 異步調(diào)用方法         // 做某些其它的事情,模擬需要執(zhí)行3 秒鐘        for (int i = 1; i <= 3; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(i));            Console.WriteLine("{0}: Client executed {1} second(s).", Thread.CurrentThread.Name, i);        }        int rtn = del.EndInvoke(asyncResult);        Console.WriteLine("Result: {0}/n", rtn);        Console.WriteLine("/nPress any key to exit...");        Console.ReadLine();    }} public class Calculator{    public int Add(int x, int y)    {        if (Thread.CurrentThread.IsThreadPoolThread)        {            Thread.CurrentThread.Name = "Pool Thread";        }         Console.WriteLine("Method invoked!");         // 執(zhí)行某些事情,模擬需要執(zhí)行2 秒鐘        for (int i = 1; i <= 2; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(i));            Console.WriteLine("{0}: Add executed {1} second(s).", Thread.CurrentThread.Name, i);        }         Console.WriteLine("Method complete!");        return x + y;    }}

此時(shí)的輸出為:

// ************************************************************************

Client application started!

Method invoked!

Main Thread: Client executed 1 second(s).

Pool Thread: Add executed 1 second(s).

Main Thread: Client executed 2 second(s).

Pool Thread: Add executed 2 second(s).

Method complete!

Main Thread: Client executed 3 second(s).

Result: 7

Press any key to exit...

// ************************************************************************

現(xiàn)在執(zhí)行完這段代碼只需要3 秒鐘時(shí)間,兩個(gè)for 循環(huán)所產(chǎn)生的輸出交替進(jìn)行,這也說(shuō)明了這兩段代碼并行執(zhí)行的情況。可以看到Add() 方法是由線程池中的線程在執(zhí)行, 因?yàn)門hread.CurrentThread.IsThreadPoolThread 返回了True,同時(shí)我們對(duì)該線程命名為了Pool Thread。另外我們可以看到通過(guò)EndInvoke()方法得到了返回值。有時(shí)候,我們可能會(huì)將獲得返回值的操作放到另一段代碼或者客戶端去執(zhí)行,而不是向上面那樣直接寫在BeginInvoke()的后面。比如說(shuō)我們?cè)赑rogram 中新建一個(gè)方法GetReturn(),此時(shí)可以通過(guò)AsyncResult 的AsyncDelegate 獲得del 委托對(duì)象,然后再在其上調(diào)用EndInvoke()方法,這也說(shuō)明了AsyncResult 可以唯一的獲取到與它相關(guān)的調(diào)用了的方法(或者也可以理解成委托對(duì)象)。所以上面獲取返回值的代碼也可以改寫成這樣:

private static int GetReturn(IAsyncResult asyncResult){    AsyncResult result = (AsyncResult)asyncResult;    AddDelegate del = (AddDelegate)result.AsyncDelegate;    int rtn = del.EndInvoke(asyncResult);    return rtn;}

然后再將int rtn = del.EndInvoke(asyncResult);語(yǔ)句改為int rtn = GetReturn(asyncResult);。注意上面IAsyncResult 要轉(zhuǎn)換為實(shí)際的類型AsyncResult 才能訪問(wèn)AsyncDelegate 屬性,因?yàn)樗鼪](méi)有包含在IAsyncResult 接口的定義中。

BeginInvoke 的另外兩個(gè)參數(shù)分別是AsyncCallback 和Object 類型,其中AsyncCallback 是一個(gè)委托類型,它用于方法的回調(diào),即是說(shuō)當(dāng)異步方法執(zhí)行完畢時(shí)自動(dòng)進(jìn)行調(diào)用的方法。它的定義為:

// ************************************************************************

public delegate void AsyncCallback(IAsyncResult ar);

// ************************************************************************

Object 類型用于傳遞任何你想要的數(shù)值,它可以通過(guò)IAsyncResult 的AsyncState 屬性獲得。下面我們將獲取方法返回值、打印返回值的操作放到了OnAddComplete()回調(diào)方法中:

using System.Threading;using System;using System.Runtime.Remoting.Messaging; public delegate int AddDelegate(int x, int y); class Program9{     static void Main(string[] args)    {        Console.WriteLine("Client application started!/n");        Thread.CurrentThread.Name = "Main Thread";        Calculator cal = new Calculator();        AddDelegate del = new AddDelegate(cal.Add);        string data = "Any data you want to pass.";         AsyncCallback callBack = new AsyncCallback(OnAddComplete);        del.BeginInvoke(2, 5, callBack, data); // 異步調(diào)用方法         // 做某些其它的事情,模擬需要執(zhí)行3 秒鐘        for (int i = 1; i <= 3; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(i));            Console.WriteLine("{0}: Client executed {1} second(s).", Thread.CurrentThread.Name, i);        }        Console.WriteLine("/nPress any key to exit...");        Console.ReadLine();    }     static void OnAddComplete(IAsyncResult asyncResult)    {        AsyncResult result = (AsyncResult)asyncResult;        AddDelegate del = (AddDelegate)result.AsyncDelegate;        string data = (string)asyncResult.AsyncState;        int rtn = del.EndInvoke(asyncResult);        Console.WriteLine("{0}: Result, {1}; Data: {2}/n", Thread.CurrentThread.Name, rtn, data);    }} public class Calculator{    public int Add(int x, int y)    {        if (Thread.CurrentThread.IsThreadPoolThread)        {            Thread.CurrentThread.Name = "Pool Thread";        }         Console.WriteLine("Method invoked!");         // 執(zhí)行某些事情,模擬需要執(zhí)行2 秒鐘        for (int i = 1; i <= 2; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(i));            Console.WriteLine("{0}: Add executed {1} second(s).", Thread.CurrentThread.Name, i);        }         Console.WriteLine("Method complete!");        return x + y;    }}

它產(chǎn)生的輸出為:

Client application started!

Method invoked!

Main Thread: Client executed 1 second(s).

Pool Thread: Add executed 1 second(s).

Main Thread: Client executed 2 second(s).

Pool Thread: Add executed 2 second(s).

Method complete!

Pool Thread: Result, 7; Data: Any data you want to pass.

Main Thread: Client executed 3 second(s).

Press any key to exit...

這里有幾個(gè)值得注意的地方:

1、我們?cè)谡{(diào)用BeginInvoke()后不再需要保存IAysncResult 了,因?yàn)锳ysncCallback 委托將該對(duì)象定義在了回調(diào)方法的參數(shù)列表中;

2、我們?cè)贠nAddComplete()方法中獲得了調(diào)用BeginInvoke()時(shí)最后一個(gè)參數(shù)傳遞的值,字符串“Any data you want to pass”;

3、執(zhí)行回調(diào)方法的線程并非客戶端線程Main Thread,而是來(lái)自線程池中的線程Pool Thread。另外如前面所說(shuō),在調(diào)用EndInvoke()時(shí)有可能會(huì)拋出異常,所以在應(yīng)該將它放到try/catch 塊中,這里就不再示范了。

 

1.8 總結(jié)

我們?cè)敿?xì)地討論了C#中的委托和事件,包括什么是委托、為什么要使用委托、事件的由來(lái)、.NET Framework 中的委托和事件、委托中方法異常和超時(shí)的處理、委托與異步編程、委托和事件對(duì)Observer 設(shè)計(jì)模式的意義。擁有了本章的知識(shí),相信你以后遇到委托和事件時(shí),將不會(huì)再有所畏懼。

文章


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 盐源县| 宁德市| 平顶山市| 蒙阴县| 江阴市| 柳河县| 太白县| 浦北县| 太原市| 广丰县| 肥城市| 辉县市| 织金县| 佛坪县| 秀山| 勐海县| 中牟县| 临沭县| 麻城市| 福贡县| 元朗区| 河池市| 青神县| 阿拉善左旗| 通许县| 湘潭县| 修文县| 上虞市| 扎囊县| 会宁县| 涞水县| 阿克陶县| 保定市| 岐山县| 永泰县| 济宁市| 怀化市| 榕江县| 久治县| 当雄县| 宜城市|