C#系列文章事件

PHPz
發布: 2017-03-12 15:43:26
原創
1128 人瀏覽過

檔案涉及的內容:

  • 設計公開事件類型

  • 編譯器如何實作事件

  • 設計偵聽事件的類型

  • 明確實作事件

##事件:

定義了事件成員的類型允許類型通知其他物件發生特定的事情。

CLR事件

模型以委託為基礎,委託是呼叫回呼方法的一種類型安全性的方式,物件憑藉著呼叫方法接收他們訂閱的通知。

定義了事件成員的型別要求能夠提供以下功能:

  1. 方法能登記它對事件的關注

  2. 方法能註銷它對事件的關注

  3. 事件發生時,登記的方法將收到通知

  4. ##本文章以一個電子郵件
應用

程式為例。當電子郵件到達時,使用者希望將郵件轉發給傳真機或尋呼機進行處理。先設計MainlManager類型來接收傳入的電子郵件,它公開NewMain事件。其他類型(Fax或Pager)物件登記對於該事件的關注。 MailManager收到新電子郵件會引發該事件,造成郵件分發給每個已登記的對象,它們都有自己的方式處理郵件。 1.1設計要公開事件的類型

第一步:定義類型來容納所有需要傳送給事件通知接收者的額外資訊

      此類型通常包含一組私有欄位以及一些用於公開這些欄位的唯讀公用

屬性

 1 class NewMailEventArgs:EventArgs 2     { 3         private readonly string m_from, m_to, m_subject; 4         public NewMailEventArgs(string from,string to,string subject) 5         { 6             m_from = from; 7             m_to = to; 8             m_subject = subject; 9         }10         public string From { get { return m_from; } }11         public string To { get{ return m_to; } }12         public string Subject { get { return m_subject; } }13     }
登入後複製

第二步:定義事件成員

#

class MailManager
    {        public event EventHandler<NewMailEventArgs> NewMail;
    }
登入後複製

其中NewMail是事件名稱。事件成員類型是EventHandler說明事件通知的所有接收者都必須提供一個原型和其委託類型匹配的回調方法。由於泛型System.EventHandler委託類型的定義如下:

       public delegate void EventHandler(

Object

sender,TEventArgs e);#Object s

end

er,TEventArgs e);#Object




################################################################1 ###所以方法原型必須有以下形式:void MethodName(Object sender,NewMailEventArgs e);之所以事件###模式###要求所有###事件處理###程式的回傳型別都是void,是因為引發事件後可能要呼叫好幾個回呼方法,但沒辦法取得所有方法的回傳值,回傳void就不允許回呼方法有回傳值。 ######第三步:定義負責引發事件的方法來通知事件的登記物件############
 1         /// <summary> 2         /// 定义负责引发事件的方法来通知事件的登记对象,该方法定义在MailManager中 3         /// 如果类是密封的,该方法要声明为私有和非虚 4         /// </summary> 5         /// <param name="e"></param> 6         protected virtual void OnNewMail(NewMailEventArgs e) 7         { 8             //出于线程安全考虑,现在将委托字段的引用复制到一个临时变量中 9             EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);10             if(temp!=null)11             {12                 temp(this, e);13             }14         }
登入後複製
###上面方法使用了Volatile.Read()方法來確保線程安全,主要考慮以下兩種情況:######1.直接判斷NewMail!=null,但在調用NewMail之前,另一個執行緒可能從委託鏈中移除了一個委託,使其為空,從而發生(NullReference###Exception###)異常。 ######2.有些人可能也會將其保存在一個臨時變量中,但未使用Volatile,理論上可以但是如果編譯器發生優化代碼移除該臨時變量,那就和第一種情況一樣。 ######使用Volatile.Read會強迫NewMail在這個呼叫發生時讀取,參考必須複製到temp變數中,比較完美的解決方式。但是在單線程的中不會出現這種情況######第四步定義方法將輸入轉換為期望事件############
1 public void SimulateNewMail(string from,string to,string subject)2         {3             //构造一个对象来容纳想传给通知接收者的信息4             NewMailEventArgs e = new NewMailEventArgs(from, to, subject);5             //调用虚方法通知对象事件已反生6             //如果没有类型重写该方法7             //我们的对象将通知事件的所有登记对象8             OnNewMail(e);9         }
登入後複製
###該方法指出一封新的郵件已到達MailManager。 ######1.2 編譯器如何實作事件######在MailManager類別中我們用一句話定義事件成員本身:public event EventHandler NewMail;#########C## ##編譯器會轉換為以下程式碼:############
         //一个被初始化为null的私有字段
        private EventHandler<NewMailEventArgs> NewMail = null;        public void add_NewMail(EventHandler<NewMailEventArgs> value)
        {            //通过循环和对CompareExchange的调用,以一种线程安全的方式向事件添加委托            //CompareExchange是把目标操作数(第1参数所指向的内存中的数)            //与一个值(第3参数)比较,如果相等,            //则用另一个值(第2参数)与目标操作数(第1参数所指向的内存中的数)交换
            EventHandler<NewMailEventArgs> prevHandler;
            EventHandler<NewMailEventArgs> newMail = this.NewMail;            do
            {
                prevHandler = newMail;
                EventHandler<NewMailEventArgs> newHandler =
                    (EventHandler<NewMailEventArgs>)Delegate.Combine(prevHandler, value);
                newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, newHandler, prevHandler);
            } while (newMail != prevHandler);
        }        public void remove_NewMail(EventHandler<NewMailEventArgs> value)
        {
            EventHandler<NewMailEventArgs> prevHandler;
            EventHandler<NewMailEventArgs> newMail = this.NewMail;            do
            {
                prevHandler = newMail;
                EventHandler<NewMailEventArgs> newHandler =
                    (EventHandler<NewMailEventArgs>)Delegate.Remove(prevHandler, value);
                newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, newHandler, prevHandler);
            } while (newMail != prevHandler);
        }
登入後複製
###本實例中,add和remove方法可訪問性都是因為事件NewMail宣告為public,而事件的可訪問性決定了什麼代碼能登記和註銷對事件的關注。但無論如何只有類型本身才能存取上述委託欄位NewMail。除了上述產生的程式碼,編譯器也會在託管程式集的元資料中產生事件定義記錄項。包含一些標誌和基礎委託類型。 CLR本身並不使用這些元資料資訊運行時只需要存取器方法。 ###

1.3 设计侦听事件的类型

如何定义一个类型来使用另一个类型提供的事件。以Fax类型为例:


internal class Fax
    {        public Fax(MailManager mm)
        {            //向MailManager的NewMail事件登记我们的回调方法
            mm.NewMail += FaxMsg;
        }        //新邮件到达,MailManager将调用这个方法        //sender表示MailManager对象,便于将信息回传给它        //e表示MailManager对象想传给我们的附加事件信息
        private void FaxMsg(object sender, NewMailEventArgs e)
        {
            Console.WriteLine("Fax 的消息from:{0} to:{1} subject:{2}", e.From, e.To, e.Subject);
        }        /// <summary>
        /// 注销        /// </summary>
        /// <param name="mm"></param>
        public void Unregister(MailManager mm)
        {
            mm.NewMail -= FaxMsg;
        }
    }
登入後複製

 电子邮件应用程序初始化时首先构造MailManager对象,并将对该对象的引用保存到变量中。然后构造Fax对象,并将MailManager对象引用作为实参传递。在Fax构造器中,使用+=登记对NewMail事件的关注。

1.4 显式实现事件

对于System.Windows.Forms.Control类型定义了大约70个事件。每个从Control派生类型创建对象都要浪费大量内存,而大多数我们只关心少数几个事件。如何通过显式实现事件来高效的实现提供了大量事件的类思路如下:

定义事件时:公开事件的每个对象都要维护一个集合(如字典)。集合将某种事件标识符作为健,将委托列表作为值。新对象构造时集合也是空白。登记对一个事件的关注会在集合中查找事件的标识符。如果事件标识符存在,新委托就和这个事件的委托列表合并,否则就添加事件标识符和委托。   

引发事件时:对象引发事件会在集合中查找事件的标识符,如果没有说明没有对象登记对这个事件的关注,所以也没委托需要回调。否则就调用与它关联的委托列表。


 1  public sealed class EventKey { } 2     public sealed class EventSet 3     { 4         //定义私有字典 5         private readonly Dictionary<EventKey, Delegate> m_events = 6             new Dictionary<EventKey, Delegate>(); 7         /// <summary> 8         /// 不存在添加,存在则和现有EventKey合并 9         /// </summary>       10         public void Add(EventKey eventKey,Delegate handler)11         {12             //确保操作唯一13             Monitor.Enter(m_events);14             Delegate d;15             //根据健获取值16             m_events.TryGetValue(eventKey, out d);17             //添加或合并18             m_events[eventKey] = Delegate.Combine(d, handler);19             Monitor.Exit(m_events);20         }21         /// <summary>22         /// 删除委托,在删除最后一个委托时还需删除字典中EventKey->Delegate23         /// </summary>       24         public void Remove(EventKey eventKey,Delegate handler)25         {26             Monitor.Enter(m_events);27             Delegate d;28             //TryGetValue确保在尝试从集合中删除不存在的EventKey时不会抛出异常29             if (m_events.TryGetValue(eventKey,out d))30             {31                 d = Delegate.Remove(d, handler);32                 if(d!=null)33                 {34                     //如果还有委托,就设置新的头部35                     m_events[eventKey] = d;36                 }37                 else38                 {39                     m_events.Remove(eventKey);40                 }41             }42             Monitor.Exit(m_events);43         }44         /// <summary>45         /// 为指定的EventKey引发事件46         /// </summary>       47         public void Raise(EventKey eventKey,Object sender,EventArgs e)48         {49             Delegate d;50             Monitor.Enter(m_events);51             m_events.TryGetValue(eventKey, out d);52             Monitor.Exit(m_events);53             if(d!=null)54             {55                 //利用DynamicInvoke,会向调用的回调方法查证参数的类型安全,56                 //并调用方法,如果存在类型不匹配,就抛异常57                 d.DynamicInvoke(new Object[] { sender, e });58             }59         }60     }
登入後複製

接下来定义类来使用EventSet


 1 public class FooEventArgs : EventArgs { } 2     public class TypeWithLotsOfEvents 3     { 4         //用于管理一组"事件/委托" 5         private readonly EventSet m_eventSet = new EventSet(); 6         //受保护的属性使派生类型能访问集合 7         protected EventSet EventSet { get { return m_eventSet; } } 8         //构造一个静态只读对象来标识这个事件 9         //每个对象都有自己的哈希码,以便在对象的集合中查找这个事件的委托链表10         protected static readonly EventKey s_fooEventKey = new EventKey();11         //定义事件访问器方法,用于在集合中增删委托12         public event EventHandler<FooEventArgs> Foo13         {14             add { m_eventSet.Add(s_fooEventKey, value); }15             remove { m_eventSet.Remove(s_fooEventKey, value); }16         }17         //为这个事件定义受保护的虚方法18         protected virtual void OnFoo(FooEventArgs e)19         {20             m_eventSet.Raise(s_fooEventKey, this, e);21         }22         //定义将输入转换成这个事件的方法23         public void SimulateFoo() { OnFoo(new FooEventArgs()); }24     }
登入後複製

如何使用TypeWithLotsOfEvent,只需按照标准的语法向事件登记即可


 1 static void Main(string[] args) 2         {           
 3             TypeWithLotsOfEvents twle = new TypeWithLotsOfEvents(); 4             twle.Foo += HandleFooEvent; 5             twle.SimulateFoo(); 6             Console.Read(); 7         } 8  9         private static void HandleFooEvent(object sender, FooEventArgs e)10         {11             Console.WriteLine("成功");12         }
登入後複製

 

以上是C#系列文章事件的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!