转载

浅谈委托和事件(一)

关于委托和事件,可能是.NET或者说是面向对象编程语言中的一个比较重要又比较难以理解的概念。关于这一话题,园子里的人也写了很多文章,最经典的可能就是张子阳的C#中的委托和事件这两篇文章了,之前也看过MSDN 上的WebCast 深入 "委托和事件" 。可能和很多人一样,刚开始读的时候,觉得很清楚,但是过了一段时间好像又忘记了委托和事件的区别,知道很久以前,在一次面试中我被问到委托和事件有什么区别,一下子就说不清了。

所以这里稍微理一下,也算是自己的一个总结。当然,还是推荐大家先读前面推荐的两篇文章。

.NET中的事件模型是建立在委托(delegate)这一机制上的,所以首先来看看什么是委托。

委托

委托是一种类型安全的调用回调方法,类似于C中的函数指针。委托(Delegate)是一个类,当创建实例时,需要传入方法名称,每一个委托都有一个签名,比如:

delegate int SomeDelegate(string s, bool b);

这就是一个委托签名,他可以代表第一个参数类型为string类型,第二个参数类型为bool型,并且返回值为int的所有方法。

当创建代理实例时,需要传入一个方法名称作为构造函数。这个方法必须和代理定义的参数和返回值类型相同。比如:

private static int SomeFunction(string str, bool bln){...}

这样我们就可以实例化一个SomeDelegate,并将SomeFunction作为参数传递进去了,因为它们有相同的签名:

SomeDelegate sd = new SomeDelegate(SomeFunction);

现在sd指向了SomeFunction这个函数。如果调用sd,那么SomeFucntion函数就会被调用。

sd("SomeString", true);

事件

定义了事件成员的类型允许类型或者类型的实例在某些特定事情发生时,通知其他对象。在平常的编程中,我们不经意间已经接触到了事件,比如Button类,当点击时,就会触发Click事件,Timer类,每一秒就会触发tick事件。要了解事件,最简单的方法就是举个例子来说明。举个通俗的猫叫,老鼠跑,主人被惊醒的例子。这也是面试时容易被问到的问题。解决方法就是使用事件,或者扩展开来就是观察者模式。

首先来实现Cat类,具体实现如下:

public class Cat {  public delegate void MeowHandler(object sender, EventArgs s);  public event MeowHandler Meow;  protected virtual void OnMeow(EventArgs s)  {   MeowHandler temp = Meow;   if (temp != null)   {    temp(this, s);   }  }  public void StartMeow()  {   EventArgs args = new EventArgs();   Console.WriteLine("Cat start meow...");   OnMeow(args);  } } 

首先定义了一个public名为MeowHandler的代理,表示猫叫。方法签名很简单,一个名为Sender的Object参数和一个EventArgs的参数,返回值为空。接着定义一个名为Meow的事件。注意这里定义事件使用event参数,然后加一个代理的名称。定义成public供外部注册。

紧接着,我们定义一个触发猫叫的方法OnMeow,用来通知所有已订阅Meow事件的对象。这里为了线程安全,在内部定义了一个temp对象,来保存Meow委托。如果不适用临时变量temp,方法只隐约Meow的话,在线程知道Meow不为null的时候,另外一个线程讲NewMail改为null,这样就会引发异常。

将OnMeow定义为虚方法使得派生类可以直接控制事件是否发生。通常派生类只需要调用基类的OnMeow方法即可。但是在有些情况下,派生类可以选择不调用基类的OnMeow方法。

最后,定义了一个StartMeow方法,用来将外部的输入转换为引发事件的动作。当我们调用StartMeow事,就会调用OnMeow方法,模拟事件发生。

接下来,需要定义Mouse类型:

public class Mouse {  public Mouse(Cat cat)  {   cat.Meow += new Cat.MeowHandler(cat_Meow);  }  void cat_Meow(object sender, EventArgs s)  {   Console.WriteLine("Mouse run...");  }  public void Unregister(Cat cat)  {   cat.Meow -= cat_Meow;  } } 

当初始化Mouse对象时,通过构造函数传入Cat对象,然后注册Cat的Meow事件。

最后,实例化一个Cat,然后将其作为参数传入Mouse的构造函数。当Cat的StartMeow方法调用时,Mouse的注册的Meow事件就会触发。

static void Main(string[] args) {     Cat c = new Cat();     Mouse m = new Mouse(c);     c.StartMeow();     Console.ReadKey(); }

区别

可以看到,对于事件,我们只能使用+=,和-=来进行注册和取消注册,无法来使用=进行赋值。在内部+=被转化为在委托链表上增加委托,-=被转化为在委托立案表上移除委托。限制了用户能够直接操作委托的权限。比如,如果没有事件,通过代理的话,只有把代理定义为public,这样,当某个用户已经注册了一系列的委托到委托列表上,正要执行的时候,另外一个用户使用赋值语句=,给予了一个新的委托,或者直接将其设置为null,那么前一个用户的委托列表就会被丢失,这样严重破坏了对象的封装性。Event使得只能使用+=和-=操作符来进行事件的注册和取消注册。

对照字段和属性,委托和事件关系似乎也是如此,它是对委托实例的一层封装,使得客户端不能随意更改或者重置内部的委托列表,只能够允许往列表中增加或者移除委托,使得代码具有更好的封装性。这样来看,事件其实只不过类似声明了一个进行了封装的委托类型变量而已。

总结

本篇文章简单介绍了委托和事件的概念,算是自己做个笔记,更好的相关介绍推荐看之前两位前辈写的文章。下一篇文章将介绍事件内部的处理机制、事件与线程安全等。

正文到此结束
Loading...