技术开发 频道

探索VB中系列事件处理程序的奥秘

  手工调用每个侦听器

  尽管RaiseEvent机制是方便的(并且也是令人觉得舒服的,如果你使用Visual Basic 6.0的话)它也有它的缺点,正如你所已经看到的。比依赖于RaiseEvent调用事件侦听器更好的是:你可以自己做这个工作。而你获得了一定的灵活性是你放弃使用RaiseEvent时的舒适为代价的。

  如果你想要完全控制事件侦听器的调用而不只是希望事情如你所愿的方法解决,你将需要利用隐式的FileFoundEventHandler委托类型。你通过调用事件委托实例本身的GetInvocationList方法重新获得一个包含所有事件的侦听器的数组。一旦你有这个列表,你就可以独立地调用每个侦听器的Invoke方法,并且捕捉由事件处理程序引发的任何异常。如果任何侦听器引发一个异常,你就能处理它并将程序继续下去。

  在Visual Basic .NET 2002 and 2003里你不能修改基于.NET的应用程序引发它们自己的内部事件的方式。(你可以在Visual Basic 2005中修改这个行为,正如你后面将要看到的)使用先于Visual Basic 2005的版本时,这里有个方法可以确保你不会在你的事件侦听器中引发问题(记住:在任何事件处理程序中一个未处理的异常将导致.NET Framework停止为当前事件调用侦听器)。为了这样做,确保你自己的事件处理不允许回滚。如果你希望通过多个事件过程获得一个单独的事件处理的话,在你的事件过程中处理所有异常以使得你不会打破事件处理程序的链条。

  使用.NET事件设计模式

  虽然如你在Visual Basic 6.0和我前面的例子中所可能做的一样引发事件并没有什么错误,.NET Framework已经为事件采用了一种特别的设计模式,一种你应该在你的应用程序中采用的设计模式。在这个模式里,所有事件提供两个参数:一个对象,提供一个对引发事件(一般命名为sender)对象的引用,和一个EventArgs对象(或者一个继承于EventArgs的对象),提供相关信息给事件(一般命名为e)。

  标准.NET Framework事件的设计模式添加了三个建议。首先,如果你的事件需要传递任何信息到它的侦听器,你应该创建一个继承于EventArgs的类并且它包含附加信息。你可以使用你的类的构造器来接受并存储信息。

  第二,提供一个引发事件的过程。大多.NET Framework类从一个重载的protected过程引发事件,一般命名为OnEventName(在FileFound事件的情况下,过程会被命名为OnFileFound)。需要引发事件的代码调用OnEventName过程,它将接着引发事件。使之成为一个protected方法意味着它可为当前类型的对象所用和基于继承自当前类的任何对象所用。使之重载意味着继承类可以改变事件的行为: 一个继承类可以添加运行于调用基类的OnEventName过程之前或之后的代码,或者可以全部跳过它们。

  第三,你可能发现创建你自己的事件委托是很有用的。尽管你无须定义一个显式事件委托就可获得事件委托,但在自己创建时可获得一些灵活性。在你创建一个委托时,你正在为过程定义一个“类型”。如果你有不止一个事件,它们需要同一套参数,创建一个定义这个类型的委托将会有用。如果你需要修改这些参数,你可以只要修改委托,而不用修改事件声明。

  举个例子,你可以声明这个FileFound事件,不用事件委托,如下:

Public Event FileFound(ByVal sender As Object, ByVal e As FileFoundEventArgs)

如果你这时想要声明其他事件,使用相同参数,你将必须重复整个声明:

Public Event FileFoundSomeOtherEvent(ByVal sender As Object, ByVal e As FileFoundEventArgs)

作为选择,你也可以声明一个新的委托类型,它描述了你的事件的参数签名:

Public Delegate Sub FileFoundEventHandler(ByVal sender As Object, ByVal e
As FileFoundEventArgs)

这时你应该声明这个类型的声明事件:

Public Event FileFound As FileFoundEventHandler Public Event FileFoundSomeOtherEvent As FileFoundEventHandler

  如果你没有采取这个额外的步骤,Visual Basic .NET编译器将为你做这些工作,添加新的委托类型到类的元数据中去。尽管你不需要使用标准.NET事件处理设计模式,它总是使得你自己的事件与内部.NET对象引发的事件匹配的最好。你获得在你的事件侦听器中“通晓”的好处,并且它们看起来像其他事件。创建你自己的事件委托是可选的,但是如果你有多个事件传递相同的参数,使用事件委托可以简化你的代码。另外,因为事件参数的更改只能在一个地方进行,所以根据你的需要在任何时候修改你的代码将会更容易。

  动态添加和移除处理程序

  到目前为止你所看到的每个事件侦听器都需要你在设计时关联到事件处理程序。Handles子句是关联使用WithEvents关键字的对象引发事件的方便而简单的方法,但是它不能在运行时提供任何弹性。另外,当多个过程处理相同的事件,Handles子句不会给你事件处理程序执行顺序的控制权。(当然,为了避免这个问题你可以使用你前面看到的技巧,遍历调用GetInvocationList返回项。这个技术要求引发事件的类的附加代码,不是在关联事件侦听器的代码中。)

  为了在何时及以什么顺序调用事件处理程序上获得完全主动,你可以使用AddHandler(和RemoveHandler)语句而不是Handles子句。AddHandler和RemoveHandler语句允许你提供一个特定的事件和准备响应事件被调用过程的地址。每个对AddHandler的调用使过程和事件相关联以使得.NET Framework在事件发生时调用过程。另外,AddHandler总是添加事件处理程序到事件调用列表的末尾。这意味着你控制了事件被处理的顺序。

  当然,如果你再多考虑片刻,你会理解当你有多个过程时,相同的事件都有一个Handles子句,Visual Basic编译器为事件处理程序创建一个多路广播委托实例而不允许你控制它们被添加进的顺序。引发的事件调用委托实例的Invoke方法,这时就按每个事件侦听器被添加的顺序(并且你无权控制这个顺序)调用它们。当你使用AddHandler和RemoveHandler语句而不是Handles子句,你只要简单控制各项加入多路广播委托的顺序。每次你的应用程序对相同事件调用AddHandler语句,你就为这个事件添加了一个新的侦听器到列表的最后。当你引发这个事件,.NET运行时按顺序调用每个侦听器。

  Visual Basic中的自定义事件

  还记得在一个事件有多个侦听器时发生的问题,还有一个事件侦听器抛出一个异常吗?这个问题有一个相当简单的解决方案,用多路广播委托实例的回调列表关联到这个事件。然而,在Visual Basic .NET 2002 和2003,这里有一些其他的事件挑战:(如果)没有复杂或低效率的代码,简单(的方法)将无法体现。Visual Basic 2005以前,事件委托类型的实例总是由Visual Basic编译器为你创建,并且编译器无法提供给你修改这个委托实例的行为。

  Visual Basic 2005为事件声明添加了新的Custom关键字。这个关键字允许你为事件的AddHandler, RemoveHandler和RaiseEvent行为提供代码。 这取决于你创建适当的委托类型及创建拥有关于事件侦听器信息的类型的实例。然而,除此之外,你有对你如何处理事件有着完全地控制。

  为了在Visual Studio 2005中创建一个自定义事件,在一个类里面放入你的游标,并为你的事件录入一个声明,如下:

Public Custom Event <YourEventName> As <EventDelegateType>

  当你完成这行代码时,编辑器将插入关联的AddHandler, RemoveHandler和RaiseEvent部分。比如,设想一下,你希望通过创建一个自定义事件处理程序来解决异常问题。例如在Visual Basic 2005版的某个类包含像这样做的一个自定义FileFound事件。代码包括适当的事件委托的一个声明,如下所示:

Public Delegate Sub FileFoundEventHandler(ByVal sender As Object, ByVal e
As FileFoundEventArgs)

  键入事件声明添加一个空的自定义事件。

  这要由你提供事件委托实例的存储(storage),并提供储存,移出并回调事件侦听器的代码。对于本例,因为代码需要能单独回调每个侦听器并捕捉及处理任何异常,FileSearch6类包括了一个储存了FileFoundEventHandler实例的泛型列表集合(generic List collection)。每次任何类为这个事件调用AddHandler,或用一个Handles子句捕捉这个事件,Visual Basic运行时引擎调用FileFound 自定义事件的AddHandler部分。代码必须添加FileFoundEventHandler以传递到泛型列表。RemoveHandler部分从内部集合里移去指定的委托实例。RaiseEvent部分调用每个委托实例的Invoke方法,捕捉和处理发生的异常。

  通过推进代码到事件声明本身,引发事件的代码不再需要担心处理异常的问题了。这就是说,与使用你前面看到的代码来引发事件不同,代码假定在Visual Basic 2005中的FileSearch6类可以简单地调用OnFileFound方法(它来引发事件)或直接调用RaiseEvent。担心异常的责任现在适得其所:它就在事件代码本身之中。这项技术直到Visual Basic 2005才可以使用。

  所有你已在本文中所看到的代码都是同步运行的。这就是说,无论FileSearch类调用委托方法本身或引发一个事件,搜索不会继续直到侦听器(或多个侦听器)完成它的的工作。如果事件处理过程暂停,显示一个警示,或返回其他疏漏,则搜索循环也将暂停。

0
相关文章