设计要公开的事件类型:流程如下所示:
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。