一、C#(.net framework框架)中的事件以及特色
程序員
二、事件的組成部分windows
三、編輯器如何實現事件的安全
四、顯式實現事件框架
CLR事件模型以委託爲基礎。使用委託來調用回調方法。聲明方式使用event關鍵字。 事件能夠理解爲在CLR中使用event關鍵字修飾的一個委託實例。編輯器
事件的特色以下所示:ide
1)、方法能登記對事件關注this
2)、方法能註銷對事件的關注spa
3)、事件發生時,登記了的方法將收到通知.net
1)、定義類型容納全部須要發送給事件通知接收者的附加信息(事件要傳遞的參數,我是這麼理解的)線程
1 /// <summary> 2 /// 發送給事件接收者的附加信息 3 /// </summary> 4 public class DailyEventArgs : EventArgs 5 { 6 private readonly string _title; 7 private readonly string _body; 8 public DailyEventArgs(string title,string body) 9 { 10 this._title = title; 11 this._body = body; 12 } 13 public string Title { get {return _title; } } 14 public string Body { get { return _body; } } 15 }
這裏解釋下EventArgs,它由FCL中定義,根據CLR事件約定,全部事件參數均要繼承自這個類,且事件的參數類名要以EventArgs結尾。它的定義以下:
2)、定義事件成員
事件成員使用event關鍵字修飾。咱們須要定義事件的可訪問級別(默認都是public);委託類型(要調用的方法的原型)。
1 class DailiesEventDemo 2 { 3 //也能夠這麼定義 4 //public delegate void CustomDelegate(object sender, DailyEventArgs e); 5 //public event CustomDelegate CustomDaily; 6 7 public event EventHandler<DailyEventArgs> NewDaily; 8 9 }
NewDaily是事件的名稱,這裏使用系統提供的委託EventHandler,EventHandler的定義以下所示,因此咱們能夠接受的方法原型爲void MethodName(Object sender,DailyEventArgs e);
3)、定義負責引起事件的方法來通知事件的的登記對象
按照CLR的約定,咱們須要定義一個受保護的虛方法。引起事件時,調用該方法。方法的參數爲咱們定義的DailyEventArgs對象,方法的默認實現是檢查是否有對象登記了對事件的關注,假若有對象登記對事件的關注,引起事件通知登記對象。
1 protected virtual void OnDendDaily(DailyEventArgs e) 2 { 3 //爲了線程安全,將委託字段存入一個臨時變量中 4 EventHandler<DailyEventArgs> temp = Volatile.Read(ref NewDaily); 5 if (temp != null) 6 temp.Invoke(this, e); 7 }
4)、定義方法將輸入轉化爲指望事件
1 /// <summary> 2 /// 定義方法將輸入轉化爲指望事件 3 /// </summary> 4 /// <param name="title"></param> 5 /// <param name="body"></param> 6 public void SimulateNewDaily(string title,string body) 7 { 8 DailyEventArgs dailyEventArgs = new DailyEventArgs(title, body); 9 OnDendDaily(dailyEventArgs); 10 }
而後讓咱們定義兩個關注類,來登記下事件(C# 用+= 來登記對事件的關注,-=註銷對事件的關注),具體代碼以下:
1 public class ZhangSan 2 { 3 public void ObtainNewDaily(object sender, DailyEventArgs e) 4 { 5 Console.WriteLine($"張三獲取一份報紙,標題是{e.Title},內容是{e.Body}"); 6 } 7 } 8 public class LiSi 9 { 10 public void ObtainNewDaily(object sender, DailyEventArgs e) 11 { 12 Console.WriteLine($"李四獲取一份報紙,標題是{e.Title},內容是{e.Body}"); 13 } 14 } 15 static void Main(string[] args) 16 { 17 try 18 { 19 DailiesEventDemo dailiesEventDemo = new DailiesEventDemo(); 20 ZhangSan zhangSan = new ZhangSan(); 21 dailiesEventDemo.NewDaily += zhangSan.ObtainNewDaily; //張三登記註冊 22 LiSi liSi = new LiSi(); 23 dailiesEventDemo.NewDaily += liSi.ObtainNewDaily; //李四登記註冊 24 dailiesEventDemo.SimulateNewDaily("人民日報", "程序員的崛起"); 25 dailiesEventDemo.NewDaily -= liSi.ObtainNewDaily; //李四取消登記註冊 26 dailiesEventDemo.SimulateNewDaily("新華日報", "How find a gril friend for Procedures MonKey ");
咱們用ILSpy來反編譯下咱們生成的程序集。
能夠看到,public event EventHandler<DailyEventArgs> NewDaily; 被編譯器翻譯成了一個具備add 和remove方法的對象,add 和remove方法中的 EventHandler<DailyEventArgs> eventHandler = this.NewDaily;拿到當前事件(被event修飾的委託)對象,而後調用 Delegate.Combine() 和 Delegate.Remove()方法添加或者移除委託連中的委託。
上面能夠看到,每個事件都會生成一個委託的實例字段(例如上面的NewDaily),在winform程序中,窗體控件(system.windows.forms.controller)具備70多個事件,爲此咱們須要準備70多個委託字段,然而咱們常常用到的事件也就幾個而已,這樣就致使了大量的委託實例字段被建立,從而浪費大量內存。 好在咱們能夠顯示實現事件:
1 public sealed class EventKey { } 2 public sealed class EventSet { 3 //私有字典維護EventKey => delegate 的映射 4 private readonly Dictionary<EventKey, Delegate> m_events = new Dictionary<EventKey, Delegate>(); 5 6 //添加EventKey => delegate映射 或者將委託和現有的EventKey合併 7 public void Add(EventKey eventKey,Delegate handler) 8 { 9 Monitor.Enter(m_events); 10 Delegate d; 11 m_events.TryGetValue(eventKey, out d); 12 m_events[eventKey] = Delegate.Combine(d, handler); 13 Monitor.Exit(m_events); 14 } 15 16 //從EvetnKey 刪除委託,而且在刪除最後一個委託時候刪除映射關係 17 public void Remove(EventKey eventKey, Delegate handler) 18 { 19 Monitor.Enter(m_events); 20 Delegate d; 21 if(m_events.TryGetValue(eventKey, out d)) 22 { 23 d = Delegate.Remove(d, handler); 24 if (d != null) 25 m_events[eventKey] = d; 26 else 27 m_events.Remove(eventKey); 28 } 29 Monitor.Exit(m_events); 30 } 31 public void Raise(EventKey eventKey,Object sender,EventArgs e) 32 { 33 Delegate d; 34 Monitor.Enter(m_events); 35 m_events.TryGetValue(eventKey, out d); 36 Monitor.Exit(m_events); 37 if(d!=null) 38 { 39 d.DynamicInvoke(new Object[] { sender, e }); 40 } 41 } 42 } 43 public class FooEventArgs : EventArgs { } 44 public class TryWithLostOfEvents 45 { 46 //定義私有實例字段來引用集合 47 //集合用於管理一組 事件/委託 對 48 //EventSet類型不是否是FCL的一部分,他是我本身的類型 49 private readonly EventSet m_eventSet = new EventSet(); 50 //受保護屬性識破愛生類能訪問集合 51 protected EventSet EventSet { get { return m_eventSet; } } 52 // 支持Foo事件的代碼,定義Foo事件的必要成員,構造靜態制度對象標識事件;每一個對象都有本身的哈希碼,一邊在對象的集合中查找這個事件的委託鏈表 53 protected static readonly EventKey s_fooEventKey = new EventKey(); 54 //定義訪問器方法,用於集合中CRUD 55 public event EventHandler<FooEventArgs> Foo 56 { 57 add { m_eventSet.Add(s_fooEventKey, value); } 58 remove { m_eventSet.Remove(s_fooEventKey, value); } 59 } 60 //調用事件的虛方法 61 protected virtual void OnFoo(FooEventArgs e) 62 { 63 m_eventSet.Raise(s_fooEventKey,this, e); 64 } 65 //定義將輸入轉換成這個事件的方法 66 public void SimulateFoo() { OnFoo(new FooEventArgs()); } 67 }
來運行一下
1 TryWithLostOfEvents tryWithLostOfEvents = new TryWithLostOfEvents(); 2 tryWithLostOfEvents.Foo += TryWithLostOfEvents_Foo; //TryWithLostOfEvents_Foo登記對事件的訂閱 3 tryWithLostOfEvents.Foo += fun1; // fun1登記對事件的訂閱 4 tryWithLostOfEvents.SimulateFoo(); 5 tryWithLostOfEvents.Foo -= fun1; // fun1註銷對事件的訂閱 6 tryWithLostOfEvents.SimulateFoo();