16. 事件

来源:互联网 发布:被网络诈骗怎么报案 编辑:程序博客网 时间:2024/04/30 14:11

16.1 事件和委托相似

事件的很多方面和委托相似。其实,事件就好像被简化的针对特殊用途的委托。

注册到事件上的方法会在事件触发时被调用。

下面是一些有关事件的重要事项。

  • 触发(raise)事件:调用(invoke)或触发(fire)事件的术语。当事件被触发时,所有注册到它的方法都会被依次调用。
  • 发布者(publisher):让事件被其他类或结构可见并使用的类或结构。
  • 订阅者(subscriber):把事件和发布者关联注册的类或结构。
  • 事件处理程序(event handler):注册到事件的方法。可以在事件所在的类或结构中,或者在不同的类或结构中。


事件有私有委托

委托和事件的行为之所以相似,是有充分理由的。事件包含了一个私有的委托。
有关事件的私有委托需要了解的重要事项如下:
  • 事件提供了对它的私有控制委托的结构化访问。
  • 与委托中的其他操作不一样,对于事件我们只可以添加、删除或调用事件处理程序。
  • 事件被触发时,它调用委托来依次调用调用列表中的方法。


16.2 源代码组件概览

需要在事件中使用的代码有5部分。

  • 委托类型声明:事件和事件处理程序必须有共同的签名和返回类型,它们通过委托类型声明进行描述。
  • 事件处理程序声明:这些在订阅者类的方法(事件处理程序)中的描述会在事件触发时被执行。它们不需要有独立的方法,它们可以是匿名方法或lambda表达式。
  • 事件声明:这个事件发布者类中的声明保存并调用事件处理程序。
  • 事件注册:这段代码把事件连接到事件处理程序。
  • 触发事件的代码:发布者类中的这段代码调用事件导致它调用事件处理程序。



16.3 声明事件

发布者类必须提供事件和触发事件的代码。

创建事件比较简单—只需要委托类型的名字。事件声明的语法如下代码所示,代码中声明了一个叫做Elapsed的事件。注意如下有关Elapsed事件的内容:

  • 声明在一个叫做MyTimerClass的类中。
  • 它接受返回类型和签名与EventHandle委托类型匹配的事件处理程序。
  • 它被声明为public,于是其他类和结构可以在这上面注册事件处理程序。

class MyTimerClass
{

public event EventHandler Elapsed;//其中 event为事件声明关键字    eventHandler为委托类型Elapsed为事件名

}

可以通过使用逗号分隔的列表在一个声明语句中声明一个以上的事件。

public event EventHandler MyEvent1,MyEvent2,MyEvent3;

我们还可以使用static关键字让事件变成静态的。

public static event EventHandler Elapsed;


事件是成员

一个常见的误解是把事件认为是类型,然而它不是。事件是成员,这一点引出了几个重要的特性。

  • 由于事件不是类型,我们不能使用对象创建表达式(new表达式)来创建它的对象。
  • 由于事件是成员:
    • 它必须声明在类或结构中,和其他成员一样;
    • 我们不能在一段可执行代码中声明事件;
  • 事件成员被隐式自动初始化为null。

委托类型和EventHandler

事件声明需要委托类型的名字,我们可以声明一个委托类型或使用已存在的。如果我们声明一个委托类型,它必须指定事件保存的方法的签名和返回类型。

一个更好的方法是,使用.NET BCL使用的并指定为事件使用标准的预定义委托类型。强烈推荐使用它,那就是EventHandler,它的声明如下代码所示:

public delegate void EventHandler(object sender,EventArgs e);



16.4 触发事件

事件成员本身只是保存了需要被调用的事件处理程序。如果事件没有被触发,什么都不会发生。我们需要确保在合适的时候有代码来做这件事情。

例如,如下代码触发了Elapsed事件。注意如下有关代码的事项:

  • 在触发事件之前和null进行比较,从而查看是否包含任何事件处理程序,如果事件是null,则表示没有。
  • 触发事件本身看起来像调用函数一样。
    • 使用事件名称,后面跟的参数列表包含在圆括号中。
    • 参数列表必须匹配事件的委托类型。

public class MyTimerClass{    public event EventHandler Elapsed; //声明事件    private void OnOneSecond(object source,EventArgs args)    {        if (Elapsed != null)            Elapsed(source,args);    }}//下面的代码确认OnOneSecond方法每1000毫秒被调用一次。    .......}

记住这些要点:

  • 发布者类有一个作为成员的事件。
  • 类包含了触发事件的代码。




16.5 订阅事件

要为事件添加事件处理程序,处理程序必须有和事件委托一致的返回类型和签名。

  • 使用+=运算符来为事件增加事件处理程序,
  • 方法可以是下面的任意一个:
    • 实例方法
    • 静态方法
    • 匿名方法
    • lambda表达式
例:下面代码为Elapsed事件增加了五个方法。

mc.Elapsed += ca.TimerHandlerA; //引用实例方法
mc.Elapsed +=ClassB.TimerHandlerB; //引用静态方法
mc.Elapsed += new EventHandler(cc.TimerHandlerC); //委托形式

mc.Elapsed +=(source,args) => //Lambda表达式

{

Console.WriteLine("Lambda expression");

};

mc.Elapsed += delegate(object source,EventArgs args)  //匿名方法

{

Console.writeLine("Anonymous method.");

};


移除事件处理程序

使用-=运算符从事件移除一个事件处理程序。




16.6 标准事件使用

GUI编程是事件驱动的,也就是说在程序运行时,它可以在任何时候被事件打断,比如按钮点击、按下按键或系统定时器。在这些情况发生时,程序需要处理事件然后继续其他事情。

显然,对于使用C#事件而言,最好的例子是异步处理程序事件。Windows GUI编程强此广泛地使用了事件,对于事件的使用,.NET框架提供了一个强烈推荐遵循的标准模式。

事件使用的标准模式的根本就是System命名空间声明的EventHandler委托类型。EventHandler委托类型的声明如下面代码所示:

  • 第一个参数用来保存触发事件的对象的引用。由于是object类型的,所以可以匹配任何类型的实例。
  • 第二个参数用来保存有关状态对于应用程序来说是否合适的状态信息。
  • 返回类型是void。

public delegate void EventHandler(object sender,EventArgs e);


使用EventArgs类

EventHandler委托类型的第二个参数是EventArgs类的对象,它声明在System命名空间中。你可能会想,既然第二个参数用于传递数据,EventArgs类的对象应该可以保存一些类型的数据。你可能错了。

  • EventArgs被设计为不能传递任何数据。它用于不需要传递数据的事件处理程序—通常会被忽略。
  • 如果你希望传递数据,必须声明一个从EventArgs继承的类,使用合适的字段来保存需要传递的数据。
尽管EventArgs类实际上并不传递数据,但它是使用EventHandler委托模式的重要一部分。不管参数使用的实际类型是什么,object类和EventArgs总是基类。这样EventHandler就能提供一个对所有事件和事件处理器都通用的签名,只允许2个参数,而不是自自都有不同签名。

通过扩展EventArgs来传递数据

为了向自己的事件处理程序的第二个参数传入数据,并且又符合标准惯例,我们需要声明一个派生自EventArgs的自定义类,它可以保存我们所需传入的数据。类的名称应该以EventArgs结尾。例如,如下代码声明了一个自定义类,它能将字符串存储在名称为Message的字段中。

public class MytCEventArgs:EventArgs

{

public string Message;  //存储Message

public MyTCEventArgs(string s)  //构造函数设置message

{

Message = s;

}

}


使用自定义委托

既然我们已经有了一个自定义类,可以让我们在事件处理程序的第二个参数中传入数据。我们需要一个委托类型来使用新的自定义类,可以有两种方式这么做。

  • 第一种方式是使用非泛型委托。实现方式如下:
    • 使用自定义的类类型创建一个新的自定义委托。
    • 在事件代码的其他部分中使用新的委托名称。

public delegate void MyTCEventHandler(object sender, MyTCEventArgs e);

  • 第二种方式是由C# 2.0引入的,使用EventHandler泛型委托的方式。要使用泛型委托,以如下方式来实现:
    • 在方括号中旋转自定义类。
    • 无论希望在哪里使用自定义委托类型的名称,都使用完整的字符串。例如,event声明是这样的:
public event EventHandler<MyTCEventArgs> Elapsed;




16.7 MyTimerClass代码

既然你已经看过了使用事件需要实现的五个组件的代码,那么我就可以展示一下代码使用的完整的MyTimerClass类。

关于类的大多数事项都很明白了—它有一个叫做Elapsedg事件可以被订阅,还有一个叫做OnOneSecond的方法每隔1秒就会被调用一次并触发事件。剩下的一个问题就是:“什么导致OnOneSecond每1秒就被调用一次?”

答案是:OnOneSecond本身就是一个事件处理程序,该事件处理程序订阅了System.Timers命名空间中Timer类的一个事件。Timer的事件每1000毫秒触发一次并调用OnOneSecond事件处理程序,然后它再触发MyTimeClass类中的Elapsed事件。

Timer类是很有用的工具,因此我会再介绍一点有关它的内容。首先,它有一个叫做Elapsed的公共事件。听上去很熟悉,因为我还用这个名字来命名了MyTimerClass中的事件。除了名字之外没有其他联系 了,当然可以为事件取其他任何名字。




16.8 事件访问器

之前提到过,+=和-=运算符是事件允许的唯一运算符。这些去处符有预定义的行为。

然而,我们可以修改这些运算符的行为,而且当使用它们时,可以让事件执行任何我们希望的自定义代码。我们可以通过为事件定义事件访问器来实现。

  • 有两个访问器:add和remove。
  • 声明事件的访问器看上去和声明一个属性差不多。
下面的示例演示了具有访问器的事件声明。两个访问器都有叫做 value的隐式值参数,它接受实例或静态方法的引用。

public event EventHandler Elapsed
{

add
{

... //执行+=运算符的代码

}
remove
{
...    //执行-=运算符的代码
}

}

声明了事件访问器之后,事件不包含任何内嵌委托对象。我们必须实现自己的机制来存储和移除事件注册的方法。

事件访问器表现为void方法,也就是不能使用会返回值的return语句。

0 0
原创粉丝点击