天天躁日日躁狠狠躁AV麻豆-天天躁人人躁人人躁狂躁-天天澡夜夜澡人人澡-天天影视香色欲综合网-国产成人女人在线视频观看-国产成人女人视频在线观看

把委托說(shuō)透(3):委托與事件

把委托說(shuō)透(1)(2)中,先后介紹了委托的語(yǔ)法和本質(zhì),本文重點(diǎn)介紹.NET中與委托息息相關(guān)的概念——事件。在此之前,首先需要補(bǔ)充(2)中遺漏的一部分內(nèi)容,即C#在語(yǔ)法上對(duì)委托鏈的支持。

C#編譯器為委托類型提供了+=和-=兩個(gè)操作符的重載,分別對(duì)應(yīng)Delegate.Combine和Delegate.Remove方法,使用這兩個(gè)操作符可以大大簡(jiǎn)化委托鏈的構(gòu)造和移除。

好了,有了+=和-=,我們就可以開(kāi)始今天的話題了。

什么是事件?

事件(event)是類型中的一種成員,定義了事件成員的類型允許類型(或者類型的實(shí)例)在某些特定事情發(fā)生的時(shí)候通知其他對(duì)象。如Button類型的Click事件,在按鈕被點(diǎn)擊的時(shí)候,程序中的其他對(duì)象可以得到一個(gè)通知,并執(zhí)行相應(yīng)的動(dòng)作。事件就是支持這種交互的類型成員

CLR中的事件模型是建立在委托這一機(jī)制之上的,這種關(guān)聯(lián)存在其必然性。

我們知道,委托是對(duì)方法的抽象,它將方法的調(diào)用與實(shí)現(xiàn)相分離。方法的調(diào)用者(即委托的執(zhí)行者)并不知道方法的內(nèi)部是如何實(shí)現(xiàn)的,而方法的實(shí)現(xiàn)者也不知道該方法會(huì)在何時(shí)被調(diào)用。

事件也是如此。事件被觸發(fā)后會(huì)執(zhí)行什么樣的操作,是由觸發(fā)者決定的,如點(diǎn)擊一個(gè)按鈕之后是插入一條記錄還是用戶登錄。事件的擁有者只知道什么情況下會(huì)觸發(fā)事件,但并不知道事件的具體實(shí)現(xiàn)。因此用委托來(lái)實(shí)現(xiàn)事件的機(jī)制就是自然而然的事情了。

事件與委托的關(guān)系到底是什么樣呢?委托是與類、接口同一級(jí)別的概念,而事件屬于類型的成員,與方法、屬性、字段等是同一級(jí)別的概念。一個(gè)與事件相關(guān)聯(lián)的委托的定義如下:

public delegate void FooEventHandler(object sender, FooEventArgs e);

而相應(yīng)事件成員的定義為:

public event FooEventHandler Foo;

可見(jiàn),事件用event關(guān)鍵字定義,其類型為一個(gè)委托類型,即事件是通過(guò)委托來(lái)實(shí)現(xiàn)的。

一個(gè)完整的事件定義和使用的例子如下:

public delegate void FooEventHandler(object sender, FooEventArgs e);public class FooEventArgs : EventArgs { }public class Bar{    public event FooEventHandler Foo;    protected virtual void OnFoo(FooEventArgs e)    {        FooEventHandler handler = Foo;        if (handler != null)            handler(this, e);    }    public void SomeMethod()    {        // ...        OnFoo(new FooEventArgs());        // ...    }}public class Client{    public Client()    {        Bar b = new Bar();        b.Foo += new FooEventHandler(b_Foo);    }    void b_Foo(object sender, FooEventArgs e)    {        throw new NotImplementedException();    }}

我們注意到在SomeMethod方法中并沒(méi)有直接調(diào)用委托,而是調(diào)用了一個(gè)輔助方法OnFoo。在該方法中,先將Foo事件的引用傳遞給新定義的委托,然后再進(jìn)行空判斷,在委托不為null的情況下才進(jìn)行調(diào)用。這樣做是為了保證線程和類型的安全,我們?cè)谙旅鎸?huì)介紹。

還有一個(gè)需要注意的地方是,客戶端為事件注冊(cè)方法時(shí),使用的是+=操作符。在本文開(kāi)頭已經(jīng)介紹,+=對(duì)應(yīng)Delegate.Combine方法,回顧(2)中闡述的委托鏈的構(gòu)造,我們可以得出如下結(jié)論:在為事件注冊(cè)方法時(shí),實(shí)際上是在構(gòu)造一個(gè)委托鏈

事件的設(shè)計(jì)規(guī)范

《Framework Design Guidelines 2nd Edition》一書應(yīng)該成為我們?cè)O(shè)計(jì).NET程序的規(guī)范手冊(cè)。書中對(duì)于事件的定義采取了如下的規(guī)定:

事件的命名

由于通常事件以為著某種行為,因此事件的名稱應(yīng)該為一個(gè)動(dòng)詞,并用動(dòng)詞的時(shí)態(tài)來(lái)指明事件發(fā)生的時(shí)間。《Framework Design Guidelines 2nd Edition》對(duì)事件命名的建議如下:

1. 用動(dòng)詞或動(dòng)詞短語(yǔ)來(lái)為事件命名。如Clicked、Painting、DroppedDown等等。

2. 用現(xiàn)在時(shí)和將來(lái)時(shí)來(lái)表示“之前”和“之后”的概念,不要用Before和Arfter前綴。例如在窗體關(guān)閉之前觸發(fā)的事件可以命名為Closing,而窗體關(guān)閉之后觸發(fā)的事件則應(yīng)該命名為Closed。

3. 為事件處理程序(委托)的名稱添加EventHandler后綴。如

4. 使用sender和e來(lái)命名時(shí)間的兩個(gè)參數(shù)。如上例。

5. 為事件的數(shù)據(jù)參數(shù)類型的名稱添加EventArgs后綴。如上例。

事件的設(shè)計(jì)

1. 通常情況下,事件所對(duì)應(yīng)的委托的返回值為void,并且包含兩個(gè)參數(shù):第一個(gè)參數(shù)為觸發(fā)事件的對(duì)象,通常為事件的擁有者(即上例中的Bar對(duì)象)。第二個(gè)參數(shù)為事件相關(guān)的數(shù)據(jù),由事件的擁有者傳遞給事件的調(diào)用者。

2. 在.NET 2.0及以后的版本中自定義事件時(shí),使用System.EventHandler委托,而不要自定義新的委托類型。因此上例中如果在.NET 2.0下應(yīng)該定義為:

public event EventHandler<FooEventArgs> Foo;

在.NET 2.0以前,由于不支持泛型,我們?nèi)匀恍枰裆厦胬又心菢佣x。

3. 為事件自定義一個(gè)EventArgs的子類,作為傳遞數(shù)據(jù)的參數(shù)。如果不需要傳遞任何參數(shù),可以直接使用EventArgs類。

4. 為每個(gè)事件編寫一個(gè)受保護(hù)的虛方法作為觸發(fā)方法,如上例中的OnFoo方法。這僅適用于unsealed類的非靜態(tài)事件,并不適用于struct、sealed class和靜態(tài)事件。這樣做的原因是,通過(guò)override為子類提供一種處理事件的方式。按照慣例,該虛方法以O(shè)n開(kāi)頭,以事件名稱結(jié)尾,如OnFoo方法。

為了確保委托在調(diào)用時(shí)不拋出NullReferenceException,在OnXxx方法中通常都會(huì)對(duì)委托進(jìn)行判空操作,如

if (Xxx != null) Xxx(this, e);

然而僅僅這樣是不夠的,因?yàn)?strong>事件處理程序的添加和移除并不是線程安全的,因此在多線程環(huán)境下,Xxx委托在判空之后很可能被Remove,導(dǎo)致Xxx在調(diào)用時(shí)可能為null。由于Remove方法將會(huì)構(gòu)造一個(gè)新的委托實(shí)例,而不會(huì)改變?cè)械囊茫虼诵枰葘⑽械囊脗鬟f給一個(gè)新的委托,再對(duì)這個(gè)新委托進(jìn)行判空和調(diào)用等操作,這樣即使原委托被Remove,也不會(huì)NullReferenceException。

FooEventHandler handler = Foo;if (handler != null) handler(this, e);

5. 觸發(fā)事件的方法有且僅有一個(gè)參數(shù),XxxEventArgs參數(shù)。

6. 在觸發(fā)非靜態(tài)事件時(shí),sender參數(shù)不要為null。對(duì)于靜態(tài)事件,sender參數(shù)要為null。

7. 觸發(fā)事件時(shí),如果不需要傳遞任何數(shù)據(jù),數(shù)據(jù)參數(shù)可以為EventArgs.Empty,不要為null。

事件的應(yīng)用舉例

在前面隨筆的評(píng)論中,有同學(xué)提出希望列舉委托在窗體間傳值的例子。好吧,我們就舉一個(gè)簡(jiǎn)單的WinForm窗體傳值的例子。

我們首先新建一個(gè)Windows From應(yīng)用程序,并新建兩個(gè)窗體MainForm和SubForm,在MainForm中建立兩個(gè)Button,在SubForm中添加一個(gè)RichTextBox。如下圖所示:

image image

當(dāng)點(diǎn)擊“開(kāi)始”的時(shí)候,會(huì)彈出SubForm,點(diǎn)擊“傳值”的時(shí)候,會(huì)將當(dāng)前時(shí)間顯示在SubForm的RichTextBox中。

需求大體就是這樣了,我們?cè)撊绾卧O(shè)計(jì)呢?

點(diǎn)擊“傳值”按鈕后,會(huì)引起SubForm的變化。SubForm只負(fù)責(zé)顯示,它并不知道引起變化的原因。MainForm負(fù)責(zé)引起變化,并將變化傳遞給SubForm,但它并不關(guān)心SubForm如何進(jìn)行處理。這與我們之前對(duì)事件的描述十分相似:

事件被觸發(fā)后會(huì)執(zhí)行什么樣的操作,是由觸發(fā)者決定的,如點(diǎn)擊一個(gè)按鈕之后是插入一條記錄還是用戶登錄。事件的擁有者只知道什么情況下會(huì)觸發(fā)事件,但并不知道事件的具體實(shí)現(xiàn)。

因此,在這個(gè)示例中,我們可以通過(guò)事件來(lái)實(shí)現(xiàn)傳值。我們首先創(chuàng)建數(shù)據(jù)參數(shù)類SendEventArgs,它包含一個(gè)Message屬性,用來(lái)保存數(shù)據(jù)。

public class SendEventArgs : EventArgs{    public string Message { get; private set; }    public SendEventArgs(string message)    {        this.Message = message;    }}

然后在MainForm中添加一個(gè)事件:Send。

public event EventHandler<SendEventArgs> Send;

然后我們?yōu)樵撌录帉懹|發(fā)方法OnSend:

protected virtual void OnSend(SendEventArgs e){    EventHandler<SendEventArgs> handler = Send;    if (handler != null)        handler(this, e);}

MainForm中兩個(gè)按鈕的事件處理程序如下:

private void btnBegin_Click(object sender, EventArgs e){    SubForm subForm = new SubForm(this);    subForm.Show();}private void btnSend_Click(object sender, EventArgs e){    SendEventArgs sendEventArgs = new SendEventArgs(DateTime.Now.ToString());    OnSend(sendEventArgs);}

btnBegin按鈕用來(lái)打開(kāi)一個(gè)SubForm,并將當(dāng)前MainForm實(shí)例作為參數(shù)傳入。btnSend按鈕用來(lái)構(gòu)造Send事件的數(shù)據(jù)參數(shù),并調(diào)用Send事件的觸發(fā)方法。

在SubForm中,有一個(gè)MainForm類型的私有字段,用于保存構(gòu)造函數(shù)里傳入的參數(shù)。

private MainForm parent;

構(gòu)造函數(shù)中除了給parent字段賦值外,還要注冊(cè)parent的Send事件的處理程序:

public SubForm(MainForm main){    InitializeComponent();    this.parent = main;    parent.Send += new EventHandler<SendEventArgs>(parent_Send);}

parent_Send處理程序負(fù)責(zé)向RichTextBox中添加信息:

private void parent_Send(object sender, SendEventArgs e){    this.rtbTime.AppendText(e.Message);    this.rtbTime.AppendText(Environment.NewLine);}

最后我們?cè)赟ubForm的Closing事件里移除parent_Send,這樣就可以打開(kāi)多個(gè)SubForm了。

private void SubForm_FormClosing(object sender, FormClosingEventArgs e){    parent.Send -= new EventHandler<SendEventArgs>(parent_Send);}

整個(gè)Demo的顯示如下:

image

總結(jié)

本文重點(diǎn)講解了.NET中的事件,并對(duì)事件的設(shè)計(jì)進(jìn)行了規(guī)范,最終通過(guò)一個(gè)示例加深了我們對(duì)事件的理解。

您是否從以上示例中感覺(jué)到了觀察者模式的影子呢?本系列接下來(lái)的一篇隨筆中,我們將會(huì)討論委托與設(shè)計(jì)模式的微妙聯(lián)系。

NET技術(shù)把委托說(shuō)透(3):委托與事件,轉(zhuǎn)載需保留來(lái)源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 国产精品久久婷婷五月色 | 97人人添人人澡人人澡人人澡 | 97国内精品久久久久久久影视 | 激情内射亚洲一区二区三区爱妻 | 最懂男人心论坛 | 第一福利在线永久视频 | 亚洲欧美日韩精品久久奇米色影视 | 99久久久国产精品免费调教 | 男人J放进女人屁股免费观看 | 久久6699精品国产人妻 | 亚洲日韩视频免费观看 | 最新国产av.在线视频 | 国产亚洲精品久久久久5区 国产亚洲精品久久久久 | 北岛玲手机在线观看视频观看 | 久久精品热线免费 | 国产91无毒不卡在线观看 | 亚洲精品乱码久久久久久中文字幕 | 亚洲AV永久无码精品澳门 | 亚洲国产精麻豆 | 久青草国产在线视频亚瑟影视 | 快播电影频道 | 高清无码中文字幕在线观看视频 | 达达兔欧美午夜国产亚洲 | 玉娇龙续集春雪瓶txt免费阅读 | 芭乐视频网页版在线观看 | 天天澡夜夜澡人人澡 | 午夜DV内射一区区 | 泡妞高手在都市免费观看 | 国产午夜三区视频在线 | 美女扒开尿口直播 | Y8848高清私人影院软件优势 | 被窝伦理午夜电影网 | 久草青青在线 | 久久精品亚洲AV中文2区金莲 | 天天干夜夜曰 | 嘟嘟嘟WWW免费高清在线中文 | 精品无码国产自产在线观看水浒传 | 青青草狠狠干 | 国产精品视频免费视频 | 草莓视频在线看免费高清观看 | 国产精品久久久亚洲偷窥女厕 |