简单化的事件与委托 Events and Delegates simplified

来源:互联网 发布:淘宝迪卡侬旗舰店 编辑:程序博客网 时间:2024/05/21 10:09

 原文地址Events and Delegates simplified

By Maysam Mahfouzi

 这篇文章简要阐明了如果为你的类设计事件。

译者序:在CodeProject上看到了2004年的一篇文章,很老了,但对于新手还是有借鉴价值的,所以也认真看了一下,感觉让我明白了C#中的事件与委托机制。不过作者并没有深入剖析该机制,但对于理解及如何运用还是蛮不错的。因为以前没有认真的做过翻译工作,所以没有将作者的那种轻松感翻译出来。但我已经尽我最大的努力去保留作者的语气,以便文章能够轻松地让读者理解C#中的事件与委托机制。

 

目录

  • 介绍
  • 什么是委托
  • 理解事件
  • 事件关键字
  • 结尾

  

  • 介绍

 

 

在我正在学习事件和委托的时候,我读了很多资料,就是为了去完全理解什么是事件和委托,以及她们是如何去使用的。现在我将的所有知道的,也是你们应该知道的一些东西呈现在这里。

  • 什么是委托

 

委托和事件是紧密联系在一起的。委托其实也就是函数指针,即存放对函数的引用。

 

一个委托就是一个类,当你创建一个他的实例时,你传入一个函数名作为她构造函数的参数,这个委托将引用这个函数。

每一个委托都有一个声明,如下就是一个委托声明:

 Delegate int SomeDelegate(string s, bool b);

 当我说到这个委托是一个声明时,我即指它返回一个int类型,并且有两个参数,一个是string类型,一个是bool类型。

我说过,当你实例化委托对象的时候,你传入一个作为其构造函数参数的函数名称时,这个委托对象将引用这个函数。在这里需要注意的是,只有与委托有着相同的声明的函数才能作为参数传递给它。

考虑如下函数:

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

你能传递这个函数给SomeDelegate,因为它们有着相似的声明。

SomeDelegate sd = new SomeDelegate(SomeFunction);

现在sd引用了函数SomeFunction,换言之,SomeFunction注册到了委托对象sd。如果sd被调用,SomeFunction同样也会被调用。请记住这里的被注册了的函数,后面我将讨论注册函数。

sd("somestring", true);

现在你已经知道如何使用委托了,下面我们一起来理解事件……

  • 理解事件
  1. 一个按钮是一个类,当你单击它的时候,click事件触发。
  2. 一个定时器也是一个类,每一个毫秒,tick事件触发

想像这样一种情况:我们有一个类名字叫做Counter,这个类有一个方法叫做CountTo(int countTo, int reachableNum),它从零开始计数到countTo,每当它到达reachableNum时,它触发一个NumberReached事件。

我们的类同样也有一个NumberReached事件。事件是一个委托类型的变量。我的意思是,如果你想声明一个事件,那么你声明一个前面带有event关键字的委托类型的变量就可以了。比如你这个样子:

public event NumberReachedEventHandler NumberReached;

在上面的声明中,NumberReachedEventHandler就是一个委托类型,可能它一个更好的名字是NumberReachedDelegate,但需要注意的是,微软从来没有说什么MouseDelegate,或者PaintDelegate,但们提供的是MouseEventHandler和PaintEventHandler。同样,用NumberReachedEventHandler是不是和用NumberReachedDelegate一样方便呢?是不是?OK!我们继续!

你看,我们前面已经定义了我们的事件,我们需要定义我们的委托或者说是事件处理器才行。它可能如下所示:

public delegate void NumberReachedEventHandler(ojbect sender, NumberReachedEventArgs e);

正如你所见,我们的委托类型名是NumberReachedEventHandler,它的声明包含object和NumberReachedEventArgs两个参数并且没有返回值。如果你在某个地方想实例化一个委托的时候,你需要一个与这个委托类型相同声明的函数作为它构造函数的参数。

你是不是曾经在代码中使用过PaintEventArgs和MouseEventArgs去取得当前鼠标的正在移动的位置,或者产生Paint事件的对象的Graphics属性。实际上,我们在类中为用户提供我们的数据是派生于EventArgs类。例如,在我们的例子中,我们想提供已经到达的计数给用户。这个类的定义就如下所示:

如果你不需要提供任何信息给用户的话,你可以直接使用EventArgs类。

现在我们已经为看Counter类内部提供了所有的准备工作,让我们来看看吧:

在上面的代码中,如果计数器达到了我们期待的数时,产生一个事件。这里,仍然有一些东西需要考虑。

1、产生事件是通过调用我事件(一个叫做NumberReachedEventHandler的委托类型的实例)来完成的:

NumberReached(this, e);

这种方式,所有已经注册的函数都将会被调用。

2、我们是通过如下方式来为注册函数提供数据的:

 

NumberReachedEventArgs = new NumberReachedEventArgs(reachableNum);

3、一个问题:为什么我们不直接通过OnNumberReached(NumberReachedEventArgs e)方法来调用NumberReached(this, e)呢?为什么我们不使用如下代码呢:

 

很好的问题!如果你想知道为什么我们要间接调用NumberReached,让我们再来看一下OnNumberReached的声明。

protected virtual void OnNumberReached(NumberReachedEventArgs e);

  • 你看,这个方法是protected声明的,这意味着它对于继承于它的类,该方法都是可以访问的。
  • 这个方法同样是带virtual声明的,这意味着它在派生类中可以被覆盖。

这是非常有用的。假设你设计了一个类继承自Counter,你可以通过覆盖OnNumberReached方法来在产生事件之前做一些额外的操作。

例如:

protected virtual void OnNumberReached(NumberReachedEventArgs e)

{

    //做一些额外的事件.......

    base.NumberReached(this, e);

}

在这里应该注意的是,如果你不调用base.NumberReached(this, e),那么这个事件将不会触发。这个可能在你想去掉父类中的一些事件的时候非常有用,啊哈,这是不是一个很有趣的技巧?

作为一个真实世界的例子,只要你创建一个新的ASP.NET应用程序之后,看一下自动生成的代码,你会发现你的页面继承自System.Web.UI.Page类,这个类有一个带有virtual和protected的声明的OnInit方法,你可以看到在Init方法里,InitializationCompontent()方法被作为一个额外的方法被调用,然后再调用了父类的OnInit()。

4、注意NumberReachedEventHandler委托类型声明在类之外,名称空间之内,对所有类是可视的。

好了,现在是时候实际使用我们的Counter类了。

在这个示例中,有两个命名为txtCountTo和txtReachable的文本框,如下的示:

 

并且,下面有一个按钮名字btnClick的单击事件的处理器:

 
下面是一个初始化一个事件处理器的语法:
oCounter.NumberReached += new NumberReachedEventHandler(oCounter_NumberReached);
现在你应该明白你在这里做了些什么了吧!你仅仅是实例化了一个NumberReachedEventHandler委托类型
对象(就像你为其它对象那样做的那样)。唯一要注意的就是oCounter_NumberReached方法的声明,这我
在前面已经提到过。并且在这里我们使用了+=符号代替了简单的 = 。
这是因为委托对象是一个特殊的对象,因为他可以拥有多个对于一个对象的引用(在这里,引用不只是一个函数)。
例如,你有一个与oCounter_NumberReached一样声明的oCounter_NumberReached2函数,这两个函数能够
以同样的方式被引用。如下所示:
oCounter.NumberReached += new NumberReachedEventHandler(oCounter_NumberReached);
oCounter.NumberReached += new NumberReachedEventHandler(oCounter_NumberReached2);
现在,这两个函数在事件触发的时候,会一过接一个的被调用。
如果在你的代码中,在某个条件下,你想oCounter_NumberReached2这个函数当NumberReached事件到达时,
不要再被调用,你可以简单的按如下方式做:
oCounter.NumberReached -= new NumberReachedEventHandler(oCounter_NumberReached2);
 
  • 事件关键字

总有那么一些人老是问:如果我没有使用event关键字,会发生什么?

本质上,声明event关键字是为了阻止任何委托用户将其设为null。为什么这么重要呢?想像一下,我作为一个客户,我将添加一个我类中的一个回调函数添加到委托解析列表。两样其它客户可样可以这么做,这一切还好。现在想像一下,某个人不是使用 += 运算符去添加委托回调,而是使用 = 运算符去设置一个委托加调。这样本质上是仍掉老的委托对象,及清空委托回调列表,并创建一个全新的带有一个委托回调函数的委托对象。这个时候,所有的其它客户将收不到回调。event关键字就是用来解决这个问题的。正常情况下,如果我不删除event关键字,试图编译下面代码时会产生错误;如果我删除event关键字,并编译如下代码,错误会消失。【译注:原英文中意思相反,我在我的VC.NET 2005上编译时得到如上结论,后面的结论也不支持原英文的意思,估计是作者笔误。】

错误 CS0070: 事件“Events.Counter.NumberReached”只能出现在 += 或 -= 的左边(从类型“Events.Counter”中使用时除外)

结论: 一个event关键字的声明在委托实例上添加了一层保护层。这个保护将阻止委托的客户重新设置委托及回调列表,并且仅仅允许从其加调列表中添加删除回调函数。

  • 结尾 

不要忘了将如下代码定义在你应用程序的主构造函数中,在这里放在onClick事件处理中只是为了示例简洁。

工程代码地址:http://download.csdn.net/source/1384547

 

原创粉丝点击