前几天和朋友老邓讨论delegate和event区别的时候,老邓问我对他们的理解,当时自己没理解清楚,只是很简单的一句话:event就是特殊的delegate,也即event是delegate的子集。并且我对老邓解释只要你愿意并完全信任调用自己代码的client,你完全可以将所有的事件用delegate代替。
后面自己仔细思考了一下,发现自己理解的局限性,确实delegate和event有很多的相似之处,并且delegate完全能实现event的功能。但我并未认证考虑为什么微软要设计这样一个限制了delegate功能的东西出来,也没从观察者角度理解events。相对delegate来说,客户端即观察者只能调用+=或者-+来添加自己对相应事件触发的通知,它不能调用new来实例化发布者的event事件如单击事件,或者直接将发布者的event对象直接赋值null从而撤销发布者所有通知列表,也不能通过调用诸如this.btn.clck(obj,e)之类的方式来触发event发布事件通知。当我们理解了观察者模式并完全站在实际对象角度考虑相信就不难理解为什么event会比delegate多这么多限制了,很显然,第一,观察者不能也不应该有权限实例化事件发布者的消息列表,同理,观察者不应该能控制事件发布者对事件的通知,这些所有的操作应该都是发布者内部的事件而不能交由外部对象来控制,因此,才产生了event对象,它是通过对delegate的限制来封装一部分本来就不应该暴露在外的行为,从而更符合面向对象的思维。我想,在发布者内部的click应该还是一个委托,不过在添加了event关键字之后,.net会通过一系列方法将这个delegate包裹起来从而封装了一部分本来就不应该暴露的行为。这样更符合面向对象的做法。
因此我认为event在本质上所做的工作应该还是通过delegate来实现的,或者至少原理相同,event关键字只不过是clr给我们对所定义的delegate对象的一个封装,这样对象可以不必暴露本来就不应该被外部对象看到的方法,如果愿意的话,我们完全可以自己去做这些封装的工作。他们最终都是使得我们所定义的对象更加符合封装的原则。
现在我们可以用代码验证这个猜想是否正确,首先看一段代码:
class Program
{
public static TestDelegate myDelegate; // 普通的委托声明
public static event TestDelegate myEvent; // 事件声明
static void Main( string [] args)
{
myDelegate += TestEvent;
myEvent += TestEvent;
myDelegate();
myEvent();
}
static void TestEvent()
{
Console.WriteLine( " Hello Event " );
}
}
代码中事件除了多了个关键字声明之外与普通委托并无不同,那么在Reflector中它们有何不同呢?
extends [mscorlib]System.Object
{
. event ConsoleApplication1.TestDelegate myEvent
{
.addon void ConsoleApplication1.Program::add_myEvent( class ConsoleApplication1.TestDelegate)
.removeon void ConsoleApplication1.Program::remove_myEvent( class ConsoleApplication1.TestDelegate)
}
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
}
.method private hidebysig static void Main( string [] args) cil managed
{
.entrypoint
}
.method private hidebysig static void TestEvent() cil managed
{
}
.field public static class ConsoleApplication1.TestDelegate myDelegate
.field private static class ConsoleApplication1.TestDelegate myEvent
}
这里我们可以看到在IL中它会首先声明一个同名普通私有字段,注意这里该字段被声明成私有的,这是为了防止外部对象非法访问这个委托,然后再看上面的event会有两个操作:
{
.addon void ConsoleApplication1.Program::add_myEvent( class ConsoleApplication1.TestDelegate)
.removeon void ConsoleApplication1.Program::remove_myEvent( class ConsoleApplication1.TestDelegate)
}
这个就是event关键字所做的封装,也就是它允许myEvent委托仅仅暴露add和remove新的委托,而该委托的其他操作都被禁止了。
上面是我自己做的一些研究,后面我读到CLR via C#时候看到对事件类似的描述,在书中第230页(英文版)中提到, 在我们声明一个事件的时候,其实编译器会帮你生成一些代码,e.g. public event EventHandler<NewMailEventArgs> NewMail;当编译器碰到这段代码时,它会把它转换成下面的代码:
private EventHandler < NewMailEventArgs > NewMail = null ;
// 2. A PUBLIC add_Xxx method (where xxx is the Event name)
// Allows objects to register interest in the event.
[MethodImpl(MethodImplOptions.Synchronized)]
public void add_NewMail(EventHandler < NewMailEventArgs > value) {
NewMail = (EventHandler < NewMailEventArgs > )
Delegate.Combine(NewMail, value);
}
// 3. A PUBLIC remove_Xxx method (where Xxx is the Event name)
// Allows objects to unregister interest in the event.
[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_NewMail(EventHandler < NewMailEventArgs > value) {
NewMail = (EventHandler < NewMailEventArgs > )
Delegate.Remove(NewMail, value);
}
这段代码跟我在Reflector中看到的IL类似。这里正是event对delegate所做的封装了。
最后谢谢文楚,由于很少分享自己所得,很多东西我并未深入研究,仅仅停留在猜想阶段就不了了。希望后面补充的东西能对别人有所帮助吧!