西西軟件園多重安全檢測下載網(wǎng)站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁編程開發(fā)C#.NET → C#事件具體實現(xiàn)步驟

C#事件具體實現(xiàn)步驟

相關軟件相關文章發(fā)表評論 來源:百度搜索時間:2012/11/14 22:49:15字體大小:A-A+

作者:西西點擊:948次評論:0次標簽: 事件

  • 類型:生活服務大。4.1M語言:中文 評分:10.0
  • 標簽:
立即下載

定義一個事件成員,表示該類型提供了如下功能:

1.能夠在事件中注冊方法 2.能夠在事件中注銷方法 3.當事件發(fā)生時,注冊的方法會被通知

(事件內(nèi)部維護了一個注冊方法列表)

CLR的事件模型是基于委托的,它可以通過類型安全的方式調(diào)用回調(diào)方法。而回調(diào)方法是訂閱事件的對象接收通知的方式。通過一個例子來說明:

①Fax對象的方法注冊到MailManager事件 ②Pager對象的方法注冊到MailManager事件 ③新的郵件到達MailManager ④MailManager對象向注冊的方法發(fā)出通知,接收通知的方法可以隨意處理。

具體實現(xiàn)步驟如下:

1.定義一個類型,能夠hold住任何發(fā)送到事件通知接收者的信息

當一個事件被觸發(fā),觸發(fā)事件的對象可能希望發(fā)送一些額外的信息給事件通知的接收對象。這些額外的信息需要封裝在它自己的類中,根據(jù)約定該類需要從System.EventArgs類派生,并且命名以EventArgs結尾。這里定義一個NewMailEventArgs類:


    public 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;
        }

        public String From { get { return m_from; } }
        public String To { get { return m_to; } }
        public String Subject { get { return m_subject; } }

    }



關于EventArgs


[ComVisible(true)]
[Serializable]
public class EventArgs
{
    public readonly static EventArgs Empty;

    static EventArgs()
    {
        EventArgs.Empty = new EventArgs();
    }

    public EventArgs()
    {
    }
}


這個類沒有實際的用途,只是作為一個基類讓其他對象繼承。很多對象不需要傳遞額外的信息,例如按鈕事件,只是調(diào)用一個回調(diào)方法就夠了。當我們定義的事件不需要傳遞額外的信息時,這時調(diào)用EventArgs.Empty就行了,不需要重新構建一個EventArgs對象。

2.定義事件成員

    public class MailManager
    {
        ...
        //NewMail事件名,
        //EventHanlder<NewMailEventArgs>,所有的事件通知接收對象必須提供給該委托類型匹配的回調(diào)方法
        public event EventHandler<NewMailEventArgs> NewMail;
    }


System.EventHandler委托的定義為:public delegate void EventHandler<TEventArgs>(Object sender, TEventArgs e) where TEventArgs: EventArgs;

為什么這里第一個參數(shù)sender的類型是Object?畢竟MailManager類型是唯一觸發(fā)這個事件的,所以可以設計成這樣:
void MethodName(MailManager sender,NewMailEventArgs e)
這種情況會有一個弊端,當sender是SmtpMailManager時,回調(diào)方法也需要改變,使用Object能夠很好的兼容。定義回調(diào)方法的參數(shù)名約定為e,這樣做主要是為了保持一致性。方便開發(fā)人員。

事件機制要求所有的事件處理方法必須返回void,這是必要的,因為一個事件可能觸發(fā)很多的回調(diào)方法,沒有辦法獲取所有的返回值,索性就不允許返回值,全部為void。有些FCL里面的事件處理程序沒有遵循,而是返回了一個Assembly類型。

3.定義一個方法來響應事件的發(fā)生

按照慣例,這個類應該定義一個protected,virtual的方法供內(nèi)部的代碼調(diào)用。這個方法接收一個NewMailEventArgs對象,這個對象包含要傳遞給消息接收方的一些信息。如下:

        protected virtual void OnNewMail(NewMailEventArgs e)
        {
            //復制一個委托的引用到臨時字段temp,這樣確保線程安全
            EventHandler<NewMailEventArgs> temp = Interlocked.CompareExchange(ref NewMail, null, null);

            //任何注冊到事件里面的方法,通知它們
            if (temp != null)
            {
                temp(this, e);
            }
        }


Tips:使用線程安全的方式觸發(fā)事件(①——>④為不斷改進的過程)

①當.NET第一次推出的時候,給開發(fā)者推薦的事件觸發(fā)方式如下:

//v1.0
protected virtual void OnNewMail(NewMailEventArgs e)
{
     if (NewMail != null)
     {
          NewMail(this, e);   
     } 
}


弊端:這里檢查了NewMail不為null才觸發(fā),但是當檢查完之后,在調(diào)用NewMail之前,有其他的線程從委托鏈中移除了一個委托,使得NewMail為null,此時會拋出異常。

②先將NewMail用一個臨時變量存起來,這時就不會因為調(diào)用時被其他線程修改而拋出異常。之所以能夠這樣做,是因為委托類型跟字符串類型一樣是不可變的。

//v2.0
protected void OnNewMail(NewMailEventArgs e)

     EventHandler<NewMailEventArgs> temp = NewMail;
     if (temp != null)
     {
          temp(this, e); 
     } 
}


弊端:可能被編譯器優(yōu)化掉本地temp變量,如果發(fā)生這種情況,就回到了第一種了。

③修復上面的bug,如下:

//v3.0
protected void OnNewMail(NewMailEventArgs e)

      EventHandler<NewMailEventArgs> temp = Thread.VolatileRead(ref NewMail);
      if (temp != null)
      {
           temp(this, e);
      }
}


這里使用VolatileRead會強制讀取temp的值,但是這里不能這樣寫,編譯不通過。但是有一個Interlocked.CompareExchange可以使用:

        //v4.0       
        protected virtual void OnNewMail(NewMailEventArgs e)
        {
            //復制一個委托的引用到臨時字段temp,這樣確保線程安全
            EventHandler<NewMailEventArgs> temp = Interlocked.CompareExchange(ref NewMail, null, null);

            //任何注冊到事件里面的方法,通知它們
            if (temp != null)
            {
                temp(this, e);
            }
        }


如果NewMail為null,CompareExchange將NewMail的值改變?yōu)閚ull,如果不為null則返回原值。換句話說,CompareExchange不會改變NewMail的值,只是以線程安全的方式返回NewMail的值,這里是一個原子操作。

第④個版本是最佳的,技術上最正確的版本。實際開發(fā)中還是可以使用第②個版本,因為JIT編譯器能夠識別這種模式而不去優(yōu)化本地的temp變量。特別地,所有微軟的JIT編譯器都遵循不會對堆引入新的讀取,因此緩存一個引用在本地變量可以確保堆引用只被訪問一次(這是沒有寫入文檔的,理論上,還是可能發(fā)生變化,所以最好選用第④版本。)

為了方便可以定義一個擴展方法來封裝:

    public static class EventArgExtensions
    {
        public static void Raise<TEventArgs>(this TEventArgs e, Object sender, ref EventHandler<TEventArgs> eventDelegate)
where TEventArgs : EventArgs
        {
            EventHandler<TEventArgs> temp = Interlocked.CompareExchange(ref eventDelegate, null, null);
            if (temp != null)
            {
                temp(sender, e);
            }
        }
    }


然后可以重寫OnNewMail:

        protected virtual void OnNewMail(NewMailEventArgs e)
        {
             e.Raise(this, ref NewMail);
        }


4.定義一個方法用來傳遞一些輸入到事件

        public void SimulateNewMail(String from, String to, String subject)
        {
            NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
            OnNewMail(e);
        }
                            
        

    相關評論

    閱讀本文后您有什么感想? 已有人給出評價!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過難過
    • 5 囧
    • 3 圍觀圍觀
    • 2 無聊無聊

    熱門評論

    最新評論

    發(fā)表評論 查看所有評論(0)

    昵稱:
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字數(shù): 0/500 (您的評論需要經(jīng)過審核才能顯示)