示例一:自定义事件类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp16
{
class Program
{
//EventHandler已在System命名空间中定义,这里直接使用。
public event EventHandler RaiseEvent;
//使用EventHandler的泛型版本,发布自定义事件参数。
public event EventHandler<CustomEventArgs> RaiseCustomEvent1;
//直接声明自定义事件与上面的泛型版本等价
public event CustomEventHandler RaiseCustomEvent2;
static void Main(string[] args)
{
var pub = new Publisher();
var sub1 = new Subscriber("sub1", pub);
var sub2 = new Subscriber("sub2", pub);
pub.DoSomething();
Console.WriteLine("Press any key to continue...");
Console.ReadLine();
}
//事件发布者
public class Publisher
{
public event EventHandler<CustomEventArgs> RaiseCustomEvent;
public void DoSomething()
{
OnRaiseCustomEvent(new CustomEventArgs("Event triggered"));
}
protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
{
EventHandler<CustomEventArgs> raiseEvent = RaiseCustomEvent;
if (raiseEvent != null)
{
e.Message += $" at {DateTime.Now}";
//raiseEvent(this, e);
//防止raiseEvent为null,可以这样写。
raiseEvent?.Invoke(this, e);
}
}
}
//事件订阅者
public class Subscriber
{
private readonly string _id;
public Subscriber(string id, Publisher pub)
{
_id = id;
pub.RaiseCustomEvent += HandleCustomEvent;
}
void HandleCustomEvent(object sender, CustomEventArgs e)
{
Console.WriteLine($"{_id} received this message: {e.Message}");
}
}
}
//自定义事件数据类
public class CustomEventArgs : EventArgs
{
public string Message { get; set; }
public CustomEventArgs(string message)
{
Message = message;
}
}
//事件是特殊类型的委托
public delegate void CustomEventHandler(object sender, CustomEventArgs args);
}
示例二:接口事件与事件访问器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp16
{
class Program
{
static void Main(string[] args)
{
Shape shape = new Shape();
Subscriber1 sub1 = new Subscriber1(shape);
Subscriber2 sub2 = new Subscriber2(shape);
shape.Draw();
Console.WriteLine("Press any key to continue...");
Console.ReadLine();
}
public interface IDrawingObject
{
event EventHandler OnDraw;
}
public interface IShape
{
event EventHandler OnDraw;
}
public class Shape : IDrawingObject, IShape
{
event EventHandler PreDrawEvent;
event EventHandler PostDrawEvent;
object objectLock = new object();
//通过提供自己的访问器,可以指定两个事件是由类中的同一个事件表示,还是由不同事件表示。
event EventHandler IDrawingObject.OnDraw
{
add
{
lock (objectLock)
{
PreDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PreDrawEvent -= value;
}
}
}
event EventHandler IShape.OnDraw
{
add
{
lock (objectLock)
{
PostDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PostDrawEvent -= value;
}
}
}
public void Draw()
{
PreDrawEvent?.Invoke(this, EventArgs.Empty);
Console.WriteLine("Drawing a shape.");
PostDrawEvent?.Invoke(this, EventArgs.Empty);
}
}
//订阅者1
public class Subscriber1
{
public Subscriber1(Shape shape)
{
IDrawingObject d = (IDrawingObject)shape;
d.OnDraw += d_OnDraw;
}
void d_OnDraw(object sender, EventArgs e)
{
Console.WriteLine("Sub1 receives the IDrawingObject event.");
}
}
//订阅者2
public class Subscriber2
{
public Subscriber2(Shape shape)
{
IShape d = (IShape)shape;
d.OnDraw += d_OnDraw;
}
void d_OnDraw(object sender, EventArgs e)
{
Console.WriteLine("Sub2 receives the IShape event.");
}
}
}
}
[ComVisible(true), Serializable]
public class EventArgs {
public static readonly EventArgs Empty = new EventArgs();
public EventArgs() { }
}
可以看出,该类型的实现非常简单,就是一个让其他类型继承的基类型。许多事件都没有附加信息需要传递。定义不需要传递附加数据的事件时,可直接使用 EventArgs.Empty,不用构造新的 EventArgs 对象。
// 自定义新邮件事件 internal 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; } } }
泛型 System.EventHandler 委托类型的定义如下:
public delegate void EventHandler<TEventArgs>(Object sender, TEventArgs e);
internal class MailManager {
public event EventHandler<NewMailEventArgs> NewMail;
protected virtual void OnNewMail(NewMailEventArgs e) {
// 出于线程安全的考虑,现在将对委托字段的引用复制到一个临时变量中
EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);
if (temp != null) temp(this, e);
}
// 版本二,调用扩展方法Raise
protected virtual void OnNewMail(NewMailEventArgs e) {
e.Raise(this, ref NewMail);
}
// 当有新邮件到达时触发事件
public void SimulateNewMail(String from, String to, String subject) {
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
OnNewMail(e);
}
}
事件处理方法原型形式:
void MethodName(Object sender, NewMailEventArgs e);
事件模式要求所有事件处理方法的返回类型都是 void。遗憾的是FCL中的一些事件处理程序(比如 ResolveEventHandler)没有遵循 Microsoft 自定的模式。
public delegate Assembly ResolveEventHandler(object sender, ResolveEventArgs args);
为方便起见,可定义扩展方法来封装这个线程安全逻辑。
public static class EventArgExtensions {
public static void Raise<TEventArgs>(this TEventArgs e,
Object sender, ref EventHandler<TEventArgs> eventDelegate) {
// 出于线程安全的考虑,现在将对委托字段的引用复制到一个临时变量中
EventHandler<TEventArgs> temp = Volatile.Read(ref eventDelegate);
if (temp != null) temp(this, e);
}
}
事件的登记和移除是线程安全的
显式实现事件
using System;
using System.Collections.Generic;
using System.Threading;
namespace ConsoleApp25
{
class Program
{
static void Main(string[] args)
{
TypeWithLotsOfEvents twle = new TypeWithLotsOfEvents();
//添加一个回调
twle.Foo += HandleFooEvent;
//证明确实可行
twle.SimulateFoo();
Console.ReadLine();
}
private static void HandleFooEvent(object sender, FooEventArgs e)
{
Console.WriteLine("Handling Foo Event here...");
}
}
// 这个类的目的是在使用EventSet时,提供
// 多一点的类型安全性和代码可维护性
public sealed class EventKey { }
public sealed class EventSet
{
// 该私有字典用于维护EventKey->Delegate映射
private readonly Dictionary<EventKey, Delegate> m_events =
new Dictionary<EventKey, Delegate>();
// 添加EventKey->Delegate映射(如果EventKey不存在),
// 或者将委托和现有的EventKey合并
public void Add(EventKey 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(EventKey eventKey, Delegate handler)
{
Monitor.Enter(m_events);
// 调用TryGetValue,确保在尝试从集合中删除不存在的EventKey时不会抛出异常
Delegate d;
if (m_events.TryGetValue(eventKey, out d))
{
d = Delegate.Remove(d, handler);
//如果还有委托,就设置新的头部(地址),否则删除EventKey
if (d != null) m_events[eventKey] = d;
else m_events.Remove(eventKey);
}
Monitor.Exit(m_events);
}
// 为指定的EventKey引发事件
public void Raise(EventKey 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)
{
// 由于字典可能包含几个不同的委托类型,
// 所以无法在编译时构造一个类型安全的委托调用
// 因此,我调用System.Delegate类型的DynamicInvoke
// 方法,以一个对象数组的形式向它传递回调方法的参数
// 在内部,DynamicInvoke会向调用的回调方法查证参数的
// 类型安全性,并调用方法
// 如果存在类型不匹配的情况,DynamicInvoke会抛出异常
d.DynamicInvoke(new Object[] { sender, e });
}
}
}
// 为这个事件定义从EventArgs派生的类型
public class FooEventArgs : EventArgs { }
public class TypeWithLotsOfEvents
{
// 定义私有实例字段来引用集合
// 集合用于管理一组“事件/委托”对
// 注意:EventSet类型不是FCL的一部分,它是自定义的类型
private readonly EventSet m_eventSet = new EventSet();
// 受保护的属性使派生类型能访问集合
protected EventSet EventSet { get { return m_eventSet; } }
#region 用于支持Foo事件的代码(为附加的事件重复这个模式)
// 定义Foo事件必要的成员
// 2a. 构造一个静态只读对象来标识这个事件
// 每个对象都有自己的哈希码,以便在对象的集合中查找这个事件委托链表
protected static readonly EventKey s_fooEventKey = new EventKey();
// 2b. 定义事件的访问器方法,用于在集合中增删委托
public event EventHandler<FooEventArgs> Foo
{
add { m_eventSet.Add(s_fooEventKey, value); }
remove { m_eventSet.Remove(s_fooEventKey, value); }
}
// 2c. 为这个事件定义受保护的虚方法OnFoo
protected virtual void OnFoo(FooEventArgs e)
{
m_eventSet.Raise(s_fooEventKey, this, e);
}
// 2d. 定义将输入转换成这个事件的方法
public void SimulateFoo() { OnFoo(new FooEventArgs()); }
#endregion
}
}