CLR via C#:事件

設計要公開的事件類型:流程如下所示:
1.定義EventArgs派生類來存儲附加數據。如果沒有附加數據時可以使用EventArgs.Empty來表示。參考代碼如下所示:

public class MyEventArgs:EventArgs
{
	private readonly int m_Age;
	private readonly string m_Name;
	
	public MyEventArgs(int age, string name)
	{
		m_Age = age;
		m_Name = name;
	}
	
	public int Age
	{
		get
		{
			return m_Age;
		}
	}
	
	public string Name
	{
		get
		{
			return m_Name;
		}
	}
}

2.使用"可訪問性修飾符 event 委託類型(也就是回調函數的原型) 事件名"來定義事件字段。參考代碼如下所示:

public event EventHandler<MyEventArgs> MyEvent;

此時編譯器會生成四部分內容。分別如下所示:
1>.一個被初始化爲null的私有委託字段。其中null表示沒有監聽者關注該事件;私有的目的是爲了防止外界惡意修改該委託字段。參考代碼如下所示:

private EventHandler<MyEventArgs> MyEvent = null;

2>.一個add_XXX函數。其中該函數的訪問權限就是事件的訪問權限;XXX名字就是事件的名字;內部可以通過循環和對CompareExchange的調用以一種線程安全的方式向事件添加委託。參考代碼如下所示:

public void add_MyEvent(EventHandler<MyEventArgs> value)
{
	EventHandler<MyEventArgs> prevHandler;
	EventHandler<MyEventArgs> myEvent = this.MyEvent;
	do
	{
		prevHandler = myEvent;
		EventHandler<MyEventArgs> newHandler = (EventHandler<MyEventArgs>)Delegate.Combine(prevHandler, value);
		myEvent = Interlocked.CompareExchange<EventHandler<MyEventArgs>>(this.MyEvent, newHandler, prevHandler);
	} while(myEvent != prevHandler);
}

3>.一個remove_XXX函數。其中該函數的訪問權限就是事件的訪問權限;XXX名字就是事件的名字;內部可以通過過循環和對CompareExchange的調用可以以一種線程安全的方式向事件移除委託。參考代碼如下所示:

public void remove_MyEvent(EventHandler<MyEventArgs> value)
{
	EventHandler<MyEventArgs> prevHandler;
	EventHandler<MyEventArgs> myEvent = this.MyEvent;
	do
	{
		prevHandler = myEvent;
		EventHandler<MyEventArgs> newHandler = (EventHandler<MyEventArgs>)Delegate.Remove(prevHandler, value);
		myEvent = Interlocked.CompareExchange<EventHandler<MyEventArgs>>(this.MyEvent, newHandler, prevHandler);
	} while(myEvent != prevHandler);
}

4>.在託管程序集的元數據中生成一個事件定義記錄項。該記錄項主要用來建立事件抽象概念和訪問器函數之間的聯繫。在C#中可以使用EventInfo類來獲取該記錄項的數據信息。

3.在EventArgs類中添加擴展函數RaiseEvent以線程安全的方式來觸發事件並傳遞附加數據。參考代碼如下所示:

public static class EventArgExtensions
{
	public static void RaiseEvent<TEventArgs>(this TEventArgs e, Object sender, ref EventHandler<TEventArgs> eventDelegate)
	{
		// 出於線程安全的考慮,現在將委託字段的引用複製到臨時字段tmp中。
		// Volatile關鍵字可以禁止編譯器將這個複製操作優化成不復制。
		EventHandler<TEventArgs> tmp = Volatile.Read(ref eventDelegate);
		// 任何方法註冊了對事件的監聽就通知它們
		if (tmp != null)
		{
			tmp(sender, e);
		}
	}
}

4.在擁有事件字段的類中定義受保護虛函數來接收附加數據,然後讓該附加數據觸發事件。函數設置成虛函數的目的是讓子類有具有觸發事件的能力。參考代碼如下所示:

protected virtual void RaiseEvent(MyEventArgs e)
{
	e.RaiseEvent(this, MyEvent);
}

5.在擁有事件字段的類中定義函數將輸入數據轉換成附加數據並傳遞給第4步。參考代碼如下所示:

public void SimulateRaiseEvent(int age, string name)
{
	MyEventArgs e = new MyEventArgs(age, name);
	RaiseEvent(e);
}

設計監聽事件的類型:流程如下所示:
1.定義回調函數,用來對監聽的事件進行邏輯處理。參考代碼如下所示:

private void MyEventHandler(Object sender, MyEventArgs e)
{
}

2.使用+=來對監聽的事件添加回調函數。其中+=等價於先創建回調函數對應的委託對象,然後調用"add_事件名"函數來添加該委託對象。參考代碼如下所示:

MyEvent += MyEventHandler;
等價於:
add_MyEvent(new EventHandler<MyEventArgs>(this.MyEventHandler));

3.使用-=來對監聽的事件移除回調函數。其中-=等價於先創建回調函數對應的委託對象,然後調用"remove_事件名"函數來移除該委託對象。參考代碼如下所示:

MyEvent -= MyEventHandler;
等價於:
remove_MyEvent(new EventHandler<MyEventArgs>(this.MyEventHandler));

顯示實現事件:由於有些類型提供了很多事件,有些是開發人員使用不到的事件,這樣就會造成內存浪費。可以自己實現一個管理委託的集合來顯示添加或者移除委託。參考代碼如下所示:

using System;
using System.Collections.Generic;
using System.Threading;

public class EventMgr
{
    // 單例對象
    private static EventMgr m_inst;
    // 私有字典用來維護EventKey -> Delegate映射
    private readonly Dictionary<string, Delegate> m_events;

    static EventMgr()
    {
        m_inst = new EventMgr();
    }

    public EventMgr()
    {
        m_events = new Dictionary<string, Delegate>();
    }

    public EventMgr Inst
    {
        get
        {
            return m_inst; 
            
        }
    }
        
    // 添加EventKey -> Delegate映射
    // 不存在時添加委託,存在時合併委託
    public void Add(string eventKey, Delegate handler)
    {
        Monitor.Enter(m_events);
        Delegate d;
        m_events.TryGetValue(eventKey, out d);
        m_events[eventKey] = Delegate.Combine(d, handler);
        Monitor.Exit(m_events);
    }
        
    // 從EventKey刪除委託
    // 刪除最後一個委託時就刪除EventKey -> Delegate映射
    public void Remove(string eventKey, Delegate handler)
    {
        Monitor.Enter(m_events);
        Delegate d;
        if (m_events.TryGetValue(eventKey, out d))
        {
            d = Delegate.Remove(d, handler);
            if (d != null)
            {
                m_events[eventKey] = d;
            }
            else
            {
                m_events.Remove(eventKey);
            }
        }
        Monitor.Exit(m_events);
    }
        
    // 爲指定的EventKey觸發事件
    public void Raise(string eventKey, Object sender, EventArgs e)
    {
        // 如果eventKey不在集合中,不拋出異常
        Delegate d;
        Monitor.Enter(m_events);
        m_events.TryGetValue(eventKey, out d);
        Monitor.Exit(m_events);

        if (d != null)
        {
            // 委託參數不匹配時,拋出異常
            d.DynamicInvoke(new object[] {sender, e});
        }
    }
}

特別注意
1.事件屬性必須同時具有add和remove訪問器函數。由於編譯器不會爲事件屬性生成委託字段,所以沒法通過事件屬性來調用委託。
2.C#中只允許使用+=和-=來添加和移除委託。其他的編程語言可能會提供事件訪問器函數(add_事件名和remove_事件名)來添加和移除委託。
3.委託對應的回調函數中一般建議將發送者類型定義成Object,名字訂定義爲sender;將附加數據類型定義成EventArgs或者EventArgs派生類型,名字定義成e。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章