.NET 中的event机制以及和delegate的区别
使用.NET也有些日子了, 但对event这个东西一直没有去深入认识. 这两天因为想在DataGridView的cell里实现一个AutoComplete的功能, 趁机了解了一下, 下面贴上一点心得.
Event机制其实是一个典型的Observer pattern: Publisher抛出一个事件, 所有订阅了该事件的Subscriber对此做出各自的响应.
简单看下这个过程:
Publisher:
{
//Publisher exposes an event for subscribers.
public event EventHandler OnPublish;
public void InvokeMe(EventArgs e)
{
//Publisher trigger this event.
if (OnPublish != null)
OnPublish(this, e);
}
}
Subscriber:
{
private Publisher m_Pub;
//Subscriber expose a property to hook up a publisher's event.
public Publisher MyPublisher
{
get { return m_Pub; }
set
{
m_Pub = value;
m_Pub.OnPublish += PublishHandler;
}
}
//Subscriber's behavior to publisher's event.
private void PublishHandler(object sender, EventArgs e)
{
Console.WriteLine(string.Format("Subscriber response to {0}",sender.ToString()));
}
}
跑一下:
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
//Subscriber subscribe a publisher.
subscriber.MyPublisher = publisher;
//Trigger publisher's event.
publisher.InvokeMe(new EventArgs());
Console.ReadLine();
}
使用reflector 查看源代码, 发现CLR自动为Publisher类生成了一个EventHandler类型的成员:
private EventHandler OnPublish;
以及两个操作方法:
{
this.OnPublish = (EventHandler) Delegate.Combine(this.OnPublish, value);
}
public void remove_OnPublish(EventHandler value)
{
this.OnPublish = (EventHandler) Delegate.Remove(this.OnPublish, value);
}
看起来OnPublish和普通的delegate对象使用上并没有什么区别. 至此,有了一个疑问: 既然使用delegate就可以实现整套Event机制了, 那么event关键字的意义何在呢?
我觉得是为了代码可读性. 一个被event修饰的delegate类型更加醒目, 提示着这个delegate类型的用途. 另外编译器会为event关键字修饰的delegate类型自动生成成员变量, 使代码看起来更加简洁直观. 不知道还有没有什么历史原因, 暂且就先认为是这两条吧.
至此, 整个Event机制比较明了了, 简单说来, 就是subscriber把自己的函数地址赋给了一个特定的publisher的函数指针变量而已. 而在.NET Framework中, 这些地址就是一个个delegate对象了. 为了简化问题, 示例没有使用自定义的EventHandler, 关于自定义EventHandler可以参考 How to: Publish Events that Conform to .NET Framework Guidelines 这篇文章.
后记:今天查阅了一下 CLR via C#. 事实上, CLR对event关键字的实现有两点好处是直接使用delegate所没有的:
第一, 注意到生成的OnPublish变量是private的, 外界对它的访问只能通过add_OnPublish和remove_OnPublish这两个方法(+=和-=两个操作符最终会被编译成对这两个方法的调用). 显然, 这样对OnPublish的封装更好, 如果直接暴露一个public的delegate, 则有可能因为一个subscriber误操作使整个委托链表被清空, 影响到其他的subscriber. 最重要的是,正如楼下“吉祥如意”所说,这样保证了event只能由类的内部触发,外界只能对event成员执行增减监听方法的操作,而不能直接触发event,这样的行为模式符合了event的设计目的。另外有一点要注意的是,因为 add_OnPublish和remove_OnPublish这两个方法是public的,所以类本身也可以为event添加处理方法,这点和“吉祥如意”所说的不同。
第二, 注意到add_OnPublish和remove_OnPublish都带有[MethodImpl(MethodImplOptions.Synchronized)]属性, 表明它们是线程安全的, 多个subscriber不能同时访问这两个方法.
我想,这两点才是使用event关键字的优势所在吧。