手记

C#基础知识:事件的讲解

在讨论事件订阅之前,我们先来看看委托的另一个特性,即调用列表(invocation list)。对于某个特定的委托而言,我们既可以将其它的委托加入其调用列表中,也可以从其调用列表中将其它的委托移除。那么当程序使用这个委托的时候,就会循环遍历并执行其调用列表中的所有委托。

下面请看一个例子,在这个例子中,我们定义了一个委托Callback,并将指代两个函数Func1和Func2的委托依次加入其调用列表中。在调用委托的时候,我们发现,Func1和Func2被依次调用。

view plaincopy to clipboardprint?

  1. class Program   

  2. {   

  3.     delegate void Callback();   

  4.   

  5.     static void Func1()   

  6.     {   

  7.         Console.WriteLine("Hello");   

  8.     }   

  9.   

  10.     static void Func2()   

  11.     {   

  12.         Thread.Sleep(3000);   

  13.         Console.WriteLine("World");   

  14.     }   

  15.   

  16.     static void Main(string[] args)   

  17.     {   

  18.         Callback cb = null;   

  19.         cb = (Callback)Callback.Combine(cb, new Callback(Func1));   

  20.         cb = (Callback)Callback.Combine(cb, new Callback(Func2));   

  21.         cb();   

  22.     }   

  23. }   

上面的代码会依次输出“Hello”和“World”。细心的读者已经发现,在输出“Hello”之后程序停顿了3秒钟,也就是说,委托的调用列表是顺序同步执行的,其执行顺序与调用列表中委托的加入顺序相同。

现在我们的讨论回到事件上来。我们看看,在C#中,事件的本质究竟是什么。在使用reflector对我们开发的EventDemo例子进行反编译后,我们可以看到,在Started和Stopped事件节点下多出了两个方法:add_xxx和remove_xxx(xxx是事件的名称)。在reflector右边的窗口中,还能看到这两个方法的源代码。


view plaincopy to clipboardprint?

  1. [MethodImpl(MethodImplOptions.Synchronized)]   

  2. public void add_Started(ServerEventHandler value)   

  3. {   

  4.     this.Started = (ServerEventHandler) Delegate.Combine(this.Started, value);   

  5. }   

view plaincopy to clipboardprint?

  1. [MethodImpl(MethodImplOptions.Synchronized)]   

  2. public void remove_Started(ServerEventHandler value)   

  3. {   

  4.     this.Started = (ServerEventHandler) Delegate.Remove(this.Started, value);   

  5. }   

原来,事件在C#中会被编译器翻译成两个方法(add_xxx和remove_xxx),在这两个方法中,会使用Delegate的Combine和Remove方法来将给定的事件处理委托添加到调用列表中。那么,当多个对象对事件进行订阅后,一旦事件触发,那么调用列表中的委托也将依次执行。注意每个方法上面的MethodImpl特性,该特性是用来保证线程安全的,防止多个线程在同一时刻访问调用列表而出现冲突(该话题今后讨论)。

为了使用的方便,C#使用“+=”和“-=”运算符实现事件订阅,事实上也就是在调用add_xxx和remove_xxx方法。这个过程由编译器自动解析并处理。请看下面的IL代码:

view plaincopy to clipboardprint?

  1. IL_0014:  callvirt   instance void [EventDemo.Lib]EventDemo.Lib.Server::add_Started(class [EventDemo.Lib]EventDemo.Lib.Server/ServerEventHandler)   

这行代码就是EventDemo示例中,Main函数订阅server实例Started事件的IL代码。明显看不到“+=”运算,取而代之的是对add_Started方法的调用。

本文简单的剖析了C#中的事件模型。事实上对于委托是如何处理调用列表这一问题,我们也可以通过reflector或者ildasm.exe来继续深究,其本身应该就是维护一个委托的数组。在下文中,将会对事件的最后一个话题:add和remove关键字进行讨论。

0人推荐
随时随地看视频
慕课网APP