一区二区久久-一区二区三区www-一区二区三区久久-一区二区三区久久精品-麻豆国产一区二区在线观看-麻豆国产视频

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

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

C#編譯器為委托類型提供了+=和-=兩個操作符的重載,分別對應Delegate.Combine和Delegate.Remove方法,使用這兩個操作符可以大大簡化委托鏈的構造和移除。

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

什么是事件?

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

CLR中的事件模型是建立在委托這一機制之上的,這種關聯存在其必然性。

我們知道,委托是對方法的抽象,它將方法的調用與實現相分離。方法的調用者(即委托的執行者)并不知道方法的內部是如何實現的,而方法的實現者也不知道該方法會在何時被調用。

事件也是如此。事件被觸發后會執行什么樣的操作,是由觸發者決定的,如點擊一個按鈕之后是插入一條記錄還是用戶登錄。事件的擁有者只知道什么情況下會觸發事件,但并不知道事件的具體實現。因此用委托來實現事件的機制就是自然而然的事情了。

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

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

而相應事件成員的定義為:

public event FooEventHandler Foo;

可見,事件用event關鍵字定義,其類型為一個委托類型,即事件是通過委托來實現的。

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

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方法中并沒有直接調用委托,而是調用了一個輔助方法OnFoo。在該方法中,先將Foo事件的引用傳遞給新定義的委托,然后再進行空判斷,在委托不為null的情況下才進行調用。這樣做是為了保證線程和類型的安全,我們在下面將會介紹。

還有一個需要注意的地方是,客戶端為事件注冊方法時,使用的是+=操作符。在本文開頭已經介紹,+=對應Delegate.Combine方法,回顧(2)中闡述的委托鏈的構造,我們可以得出如下結論:在為事件注冊方法時,實際上是在構造一個委托鏈。

事件的設計規范

《Framework Design Guidelines 2nd Edition》一書應該成為我們設計.NET程序的規范手冊。書中對于事件的定義采取了如下的規定:

事件的命名

由于通常事件以為著某種行為,因此事件的名稱應該為一個動詞,并用動詞的時態來指明事件發生的時間。《Framework Design Guidelines 2nd Edition》對事件命名的建議如下:

1. 用動詞或動詞短語來為事件命名。如Clicked、Painting、DroppedDown等等。

2. 用現在時和將來時來表示“之前”和“之后”的概念,不要用Before和Arfter前綴。例如在窗體關閉之前觸發的事件可以命名為Closing,而窗體關閉之后觸發的事件則應該命名為Closed。

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

4. 使用sender和e來命名時間的兩個參數。如上例。

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

事件的設計

1. 通常情況下,事件所對應的委托的返回值為void,并且包含兩個參數:第一個參數為觸發事件的對象,通常為事件的擁有者(即上例中的Bar對象)。第二個參數為事件相關的數據,由事件的擁有者傳遞給事件的調用者。

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

public event EventHandler<FooEventArgs> Foo;

在.NET 2.0以前,由于不支持泛型,我們仍然需要像上面例子中那樣定義。

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

4. 為每個事件編寫一個受保護的虛方法作為觸發方法,如上例中的OnFoo方法。這僅適用于unsealed類的非靜態事件,并不適用于struct、sealed class和靜態事件。這樣做的原因是,通過override為子類提供一種處理事件的方式。按照慣例,該虛方法以On開頭,以事件名稱結尾,如OnFoo方法。

為了確保委托在調用時不拋出NullReferenceException,在OnXxx方法中通常都會對委托進行判空操作,如

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

然而僅僅這樣是不夠的,因為事件處理程序的添加和移除并不是線程安全的,因此在多線程環境下,Xxx委托在判空之后很可能被Remove,導致Xxx在調用時可能為null。由于Remove方法將會構造一個新的委托實例,而不會改變原委托的引用,因此需要先將委托的引用傳遞給一個新的委托,再對這個新委托進行判空和調用等操作,這樣即使原委托被Remove,也不會NullReferenceException。

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

5. 觸發事件的方法有且僅有一個參數,XxxEventArgs參數。

6. 在觸發非靜態事件時,sender參數不要為null。對于靜態事件,sender參數要為null。

7. 觸發事件時,如果不需要傳遞任何數據,數據參數可以為EventArgs.Empty,不要為null。

事件的應用舉例

在前面隨筆的評論中,有同學提出希望列舉委托在窗體間傳值的例子。好吧,我們就舉一個簡單的WinForm窗體傳值的例子。

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

image image

當點擊“開始”的時候,會彈出SubForm,點擊“傳值”的時候,會將當前時間顯示在SubForm的RichTextBox中。

需求大體就是這樣了,我們該如何設計呢?

點擊“傳值”按鈕后,會引起SubForm的變化。SubForm只負責顯示,它并不知道引起變化的原因。MainForm負責引起變化,并將變化傳遞給SubForm,但它并不關心SubForm如何進行處理。這與我們之前對事件的描述十分相似:

事件被觸發后會執行什么樣的操作,是由觸發者決定的,如點擊一個按鈕之后是插入一條記錄還是用戶登錄。事件的擁有者只知道什么情況下會觸發事件,但并不知道事件的具體實現。

因此,在這個示例中,我們可以通過事件來實現傳值。我們首先創建數據參數類SendEventArgs,它包含一個Message屬性,用來保存數據。

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

然后在MainForm中添加一個事件:Send。

public event EventHandler<SendEventArgs> Send;

然后我們為該事件編寫觸發方法OnSend:

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

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

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按鈕用來打開一個SubForm,并將當前MainForm實例作為參數傳入。btnSend按鈕用來構造Send事件的數據參數,并調用Send事件的觸發方法。

在SubForm中,有一個MainForm類型的私有字段,用于保存構造函數里傳入的參數。

private MainForm parent;

構造函數中除了給parent字段賦值外,還要注冊parent的Send事件的處理程序:

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

parent_Send處理程序負責向RichTextBox中添加信息:

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

最后我們在SubForm的Closing事件里移除parent_Send,這樣就可以打開多個SubForm了。

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

整個Demo的顯示如下:

image

總結

本文重點講解了.NET中的事件,并對事件的設計進行了規范,最終通過一個示例加深了我們對事件的理解。

您是否從以上示例中感覺到了觀察者模式的影子呢?本系列接下來的一篇隨筆中,我們將會討論委托與設計模式的微妙聯系。

NET技術把委托說透(3):委托與事件,轉載需保留來源!

鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。

主站蜘蛛池模板: 黄色高清在线观看 | 麻豆精品成人免费国产片 | 久久国产精品免费一区二区三区 | 中文字幕亚洲图片 | 怡红院视频网 | 色优久久 | 五月天丁香婷婷开心激情五月 | 日韩三级一区二区三区 | 久久精品免视看国产成人2021 | 中文字幕一区二区视频 | 特黄视频免费看 | 国产高清第一页 | 精品视频一区二区 | 亚洲美日韩 | 91色在线观看国产 | 香蕉免费看一区二区三区 | 欧美日韩乱国产 | 国产精品社区在线观看 | 亚洲乱亚洲乱妇41p国产成人 | 青草99| 国产视频第一页 | 手机看片www xiao2b cm | 521香蕉永久播放地址 | 亚洲精品综合在线 | 一级做a爰片性色毛片武则天五则 | 天天干天天操天天舔 | 萝控喷水视频 | 亚洲精品乱码国产精品乱码 | 精品国产高清在线看国产 | 在线成人小视频 | 国产黄色自拍视频 | 好吊妞视频998www | 国产aⅴ一区二区三区 | 女人笫一次一级毛片 | 韩国精品一区二区三区 | 亚洲精品小视频 | 国产精品12p | 国产精品福利久久 | 国产精品欧美一区二区三区不卡 | 羞羞色男人的天堂伊人久久 | 亚洲综合色就色手机在线观看 |