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

首頁 > 學院 > 開發設計 > 正文

[CLR via C#]11. 事件

2019-11-17 03:21:01
字體:
來源:轉載
供稿:網友

[CLR via C#]11. 事件

一、 設計要公開事件的類型

  如果類型定義了事件成員,那么類型(或類型實例)就可以通知其他對象發生了一些特定的事情。

  例如,Button類提供了一個名為Click的事件。應用程序中的一個或多個對象可能想接受這個事件的通知,以便在Button被單擊之后采取某些操作。事件就是實現這種交互的類型成員。

  如果定義了一個事件成員,意味著類型要提供一下能力:  1)方法可登記它對該事件的關注。  2)方法可注銷它對該事件的關注。  3)該事件發生時,登記了的方法會受到通知。  類型之所以能提供事件通知功能,是因為類型維護了一個已登記方法的列表。事件發生后,類型將通知列表中所有已登記的方法。    CLR的事件模型建立在委托的基礎上。委托是調用(Invoke)回調方法的一種類型安全的方式。對象憑借回調方法接受它們訂閱的通知。  先描述一個應用場景,當收到新郵件時,需要做出的響應。如圖11-1  第一步:定義類型來容納所有需要發送給事件通知接收者的附加信息  事件引發時,引發事件的對象可能希望向事件通知的對象傳遞一些附加的信息。這些附加的信息需要封裝到它自己的類中,該類通常包含一組私有字段,以及一些用于公開這些字段的只讀公共屬性。根據約定,這類應該從System.EventArgs派生,并且類名應該以EventArgs結束。在本例中,該類命名為NewMailEventArgs類,它的各個字段分別標識了郵件發件人(m_from)、郵件收件人(m_to)和郵件主題(m_subject)。
/// <summary>/// 第一步:定義類型來容納所有需要發送給事件接收者的附加信息/// </summary>internal sealed class NewMailEventArgs : EventArgs{     PRivate readonly String m_from, m_to, m_subject;     public NewMailEventArgs(String from, String to, String subject)    {        m_from = from; m_to = to; m_subject = subject;    }    /// <summary>    /// 郵件發件人    /// </summary>    public String From { get { return m_from; } }    /// <summary>    /// 郵件收件人    /// </summary>    public String To { get { return m_to; } }    /// <summary>    /// 郵件主題    /// </summary>    public String Subject { get { return m_subject; } }}//后續的步驟在MailManager類中進行internal class MailManager {}

第二步 :定義事件成員

  事件成員使用C#關鍵字event來定義。每個事件成員都要指定以下內容:一個可訪問性標識符(幾乎都是public,這樣其他代碼才能訪問該事件成員);一個委托類型,它指出要調用的方法的原型;以及一個任意事件名。以下是我們的MailManager類中的事件成員:
internal class MailManager {    // 第二步:定義事件成員    public event EventHandler<NewMailEventArgs> NewMail;    ......}
  NewMail是這個事件的名稱。事件成員的類型是EventHandler<NewMailEventArgs>,意味著"事件通知"的所有接收者都必須提供一個原型和EventHandler<NewMailEventArgs>委托類型匹配的回調方法。由于泛型System.EventHandler委托類型的定義如下:
public delegate void Eventhandler<TEventArgs>(Object sender, TEventArgs e) where TEventArgs: EventArgs;

  所以方法原型必須具有以下形式:

void MethodName(Object sender, TEventArgs e);

第三步:定義負責引發事件的方法來 通知事件的登記對象  根據約定,類應該定義一個受保護的虛方法。要引發事件時,當前類及其派生類中的代碼會調用該方法。該方法要獲取一個參數,也就是一個NewMailEventArgs對象。在這個對象中,包含了傳給通知接受對象的信息。該方法的默認實現只檢查一下是否有對象登記了對該事件的關注。
internal class MailManager{   ......    /// <summary>    /// 第三步:定義負責引發事件的方法來 通知事件的登記對象    /// 如果類是密封的,這個方法要聲明為私有和非虛    /// </summary>    /// <param name="e"></param>    protected virtual void OnNewMail(NewMailEventArgs e)    {        // 處于線程安全考慮,現在將對委托字段的引用復制到一個臨時字段中        EventHandler<NewMailEventArgs> temp = NewMail;         // 任何方法登記了對事件的關注,就通知它們        if (temp != null)        {            temp(this, e);        }    }    .....}

第四步:定義方法將輸入轉換為期望事件  你的類還必須有一個方法獲取一些輸入,并把它轉換為事件的引發。在MailManager的例子中,是調用SimulateNewMail方法指出一封新的電子郵件已到達MailManager:
internal class MailManager {    ......    //  第四步:定義方法將輸入轉換為期望事件   public void SimulateNewMail(String from, String to, String subject) {       // 構造一個對象來容納想傳給通知接收者的消息      NewMailEventArgs e = new NewMailEventArgs(from, to, subject);       // 調用虛方法通知對象事件已發生      OnNewMail(e);   }    ......}

二、 編譯器如何實現事件

  我們仔細扒一扒事件是什么?它是如何工作的。

  在MailManager類中,我們用一行代碼定義了事件成員:
public event EventHandler<NewMailEventArgs> NewMail;

  我們來看看發生了什么?

  C#編譯器在編譯這行代碼時,它會把它轉換為3個構造:
 // 1. 一個被初始化為null的私有委托字段  private EventHandler<NewMailEventArgs> NewMail= null;   //2. 一個公共add_Xxx方法(其中Xxx是事件名)  //允許方法登記對事件的關注  public void add_NewMail(EventHandler<NewMailEventArgs> value){    //通過循環對CompareExchange的調用    //可以以一種線程安全的方式向事件添加委托    EventHandler<NewMailEventArgs> handler2;    EventHandler<NewMailEventArgs> newMail = this.NewMail;    do    {        handler2 = newMail;        EventHandler<NewMailEventArgs> handler3 = (EventHandler<NewMailEventArgs>) Delegate.Combine(handler2, value);        newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, handler3, handler2);    }    while (newMail != handler2);} //3. 一個公共remove_Xxx方法(其中Xxx是事件名)//允許方法注銷對事件的關注public void remove_NewMail(EventHandler<NewMailEventArgs> value){    //通過循環對CompareExchange的調用    //可以以一種線程安全的方式向事件移除委托    EventHandler<NewMailEventArgs> handler2;    EventHandler<NewMailEventArgs> newMail = this.NewMail;    do    {        handler2 = newMail;        EventHandler<NewMailEventArgs> handler3 = (EventHandler<NewMailEventArgs>) Delegate.Remove(handler2, value);        newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, handler3, handler2);    }    while (newMail != handler2);}

  第一個構造是具有恰當委托類型的字段。該字段是對一個委托列表的頭部的引用。事件發生時會通知這個列表中的委托。字段初始化為null,表示無監聽者登記對該事件的關注。

  注意,即使原始代碼中將事件定義為public,委托字段(本例是NewMail)也始終是private。  第二個構造是一個方法,它允許其他對象登記對該事件的關注。C#編譯器在事件名(NewMail)之前附加了add_前綴,從而自動命名該方法。C#編譯器還自動為方法生成代碼。生成的代碼總是調用System.Delegate的靜態Combine方法,它將委托事實例添加到委托列表中,返回新的列表頭(地址),并將這個地址存回字段。  第三個構造是一個方法,它允許其他對象移除對該事件的關注。C#編譯器在事件名(NewMail)之前附加了remove_前綴,從而自動命名該方法。C#編譯器還自動為方法生成代碼。生成的代碼總是調用System.Delegate的靜態Remove方法,它將委托事實例從委托列表中移除,返回新的列表頭(地址),并將這個地址存回字段。  除了生成上述3個構造,編譯器還會在托管程序集的元數據中生成一個事件定義記錄項。這個記錄項包含了一些標志(flag)和基礎委托類型,還引用了add和remove訪問器方法。這些信息的作用就是建立"事件"的抽象概念和它的訪問器方法之間的聯系。編譯器和其他工具可利用這些元數據信息,并可通過System.Reflection.EventInfo類獲取這些信息。但是,CLR本身并不使用這些元數據信息,它在運行時只需要訪問器方法。三、設計偵聽事件的類型演示如何定義一個了使用另一個類提供的事件。
internal sealed class Fax    {        /// <summary>        /// 將MailManager對象傳給構造器        /// </summary>        /// <param name="mm"></param>        public Fax(MailManager mm)        {            // 構造EventHandler<NewMailEventArgs>委托的一個實例,            // 使它引用我們的FaxMsg回調方法            // 向MailManager的NewMail時間等級我們的回調方法            mm.NewMail += FaxMsg;        }         /// <summary>        /// 新郵件到達時,MailManager將調用這個方法        /// </summary>        /// <param name="sender">MailManager對象,便于回調</param>        /// <param name="e">NewMailEventArgs對象</param>        private void FaxMsg(Object sender, NewMailEventArgs e)        {            Console.WriteLine("Faxing mail message:");            Console.WriteLine("   From={0}, To={1}, Subject={2}",               e.From, e.To, e.Subject);        }         // 執行這個方法,Fax對象指向NewMail事件注銷自己對它的關注,以后便不會接受通知        public void Unregister(MailManager mm)        {            // 向MailManagerde的NewMail事件注銷自己對這個事件的關注            mm.NewMail -= FaxMsg;        }    }

  在Fax構造器中,Fax對象使用C#的+=操作符登記它對MailManager的NewMail事件的關注。

mm.NewMail += FaxMsg;

  因為C#編譯器內建對事件的支持,所以會將+=操作符翻譯成為以下代碼來添加對事件的關注:

mm.add_NewMail (new EventHandler<NewMailEventArgs>(this.FaxMsg));

  即使使用的編程語言不直接支持事件,也可以顯示調用add訪問器方法向事件登記一個委托。兩者效果是相同的,后者只是源代碼看起來沒那么優美而已。兩者最終都是用add訪問器將委托添加到事件的委托列表中,從而完成委托向事件的登記。

  在Unregister方法中,我們使用了"-="來向MailManagerde的NewMail事件注銷自己對這個事件的關注,當C#編譯器看到使用"-="操作符時向事件注銷一個委托時,會生成對事件的remove方法的調用:

mm.remove_NewMail (new EventHandler<NewMailEventArgs>(this.FaxMsg));

  和"+="一樣,即使使用的編程語言不直接支持事件,也可以顯示調用remove訪問器方法向事件注銷一個委托。remove方法為了向事件注銷委托,需要掃描委托列表來尋找恰當的委托。如發現一個匹配,該委托會從事件的委托列表中刪除。如果沒發現匹配,那么不會報錯,列表不會發生變化。

  C#要求代碼使用+=和-=操作符在列表中增刪委托。

四、顯式實現事件

  System.Windows.Forms.Control類型定義了約70個事件。假如Control類型在實現事件時,是允許編譯器隱式生成add和remove訪問器方法以及委托字段,那么每個Control對象都會包含70個委托字段。由于大多數開發人員只關心少數幾個事件,所以從Control派生類型創建的對象都會浪費大量內存。

  目前。討論C#編譯器如何允許類的開發人員顯式實現一個事件,使開發人員能夠控制add和remove方法來操縱回調委托的方式。這里演示如何通過顯式實現事件來高效率地實現一個提供大量事件的類。

  為了高效率存儲事件委托,公開了事件每個對象都要維護的一個集合(通常是一個字典)。這個集合將某種形式的事件標識作為鍵(key)。新對象構造時,這個集合是空白的。登記對一個事件的關注時,會在集合中查找事件的標識符。如果事件標識符已在其中,新委托就和這個事件的委托列表合并。如果事件標識符不再集合中,就添加事件標識符和委托。

  對象需要引發一個事件時,會在集合中查找事件標識符。如果集合中沒有找到事件標識符,表明還沒有任何對象登記對這個事件的關注,所以沒有任何委托需要回調。如果事件標識符在集合中,就調用它關聯的委托列表。這個設計模式的實現是定義了事件那個類型的開發人員的責任;使用類型的開發人員不知道事件在內部是如何實現的。

  下面展示了如何完成這個模式的。首先實現的是一個EventSet類,它代表一個集合,其中包含了事件以及

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 米脂县| 托里县| 南昌县| 竹北市| 仲巴县| 盐城市| 内黄县| 江源县| 昭苏县| 九江市| 来凤县| 崇明县| 张家港市| 惠东县| 三门峡市| 敦煌市| 阿克苏市| 星座| 阿拉尔市| 盐津县| 平昌县| 绥阳县| 新密市| 秦皇岛市| 天气| 昆山市| 鄂州市| 红桥区| 梧州市| 通州区| 二连浩特市| 昌宁县| 林周县| 读书| 临猗县| 泗洪县| 罗甸县| 黑龙江省| 滨海县| 石渠县| 秀山|