研究异常(Exception)行为
我将很快给你看看从FileSearch3类中选取的代码。每次一个类的实例找到一个文件,实例就引发它的FileFound事件。这一效果导致.NET Framework相继地运行每个事件处理过程。看看下面的代码:
’’搜索符合的文件名
Dim afi() As FileInfo = diLocal.GetFiles(Me.FileSpec)For Each fi As FileInfo In afi
’’这相当于:
’’ FileFoundEventHandler.Invoke(fi)
RaiseEvent FileFound(fi)
Next
alFiles.AddRange(afi)
如果查看一下IL为示例代码所生成的,情况就变得清楚了。图 6 显示了在ILDASM中FileSearch3.Search方法的反汇编。你在类中看到的RaiseEvent语句直到一个FileFoundEventHandler.Invoke方法调用后才会编译。在内部,一旦这个方法已经调用,你的代码将控制权交给委托的执行过程,同时如果一个未处理的异常在调用列表的任何地方发生,这个异常回滚到调用者(这个代码),同时再没有侦听器获得调用。

这里有一个方案。胜于一再地简单调用RaiseEvent语句,它可能为你而显式地调用每个单独的侦听器。你可以利用Delegate类的成员以解决在多侦听器下的未处理异常问题。
手工调用每个侦听器
尽管RaiseEvent机制是方便的(并且也是令人觉得舒服的,如果你使用Visual Basic 6.0的话)它也有它的缺点,正如你所已经看到的。比依赖于RaiseEvent调用事件侦听器更好的是:你可以自己做这个工作。而你获得了一定的灵活性是你放弃使用RaiseEvent时的舒适为代价的。
如果你想要完全控制事件侦听器的调用而不只是希望事情如你所愿的方法解决,你将需要利用隐式的FileFoundEventHandler委托类型。你通过调用事件委托实例本身的GetInvocationList方法重新获得一个包含所有事件的侦听器的数组。一旦你有这个列表,你就可以独立地调用每个侦听器的Invoke方法,并且捕捉由事件处理程序引发的任何异常。如果任何侦听器引发一个异常,你就能处理它并将程序继续下去。
FileSearch4类包含其Search方法中的代码如图 7 显示。通过点击示例窗体上的GetInvocationList按钮运行这个代码。正如你将看到的,示例仍然调用引发一个错误的事件侦听器,但是既然这样,代码就不会在第一次侦听器引发错误时停止搜索文件。因为FileSearch4.Search方法包括独立地调用每个侦听器的代码,它也可以为每个调用处理异常。
Dim ListenerList() As System.Delegate
Dim Listener As FileFoundEventHandler
ListenerList = FileFoundEvent.GetInvocationList()
’ Search for matching file names.
Dim afi() As FileInfo = diLocal.GetFiles(Me.FileSpec)
For Each fi As FileInfo In afi
For Each Listener In ListenerList
Try
Listener.Invoke(fi)
Catch
’ Something goes wrong? Just move on to
’ the next event handler.
End Try
Next
alFiles.AddRange(afi)
Next
* 声明一个System.Delegate类型的数组以使得代码可以追踪到事件的所有侦听器:
For Each fi As FileInfo In afi
’’ Code removed here...
Next
Try
Listener.Invoke(fi)
Catch
’’ 出了什么错?只是前进到下一个事件处理程序
End Try
Next
这个示例项目既没有声明FileFoundEventHandler类型也没有声明FileFoundEvent变量。Visual Basic .NET编译器在你的代码中发现事件声明时它会创建这些条目。尽管你可以通过自己声明这些对象以去处含糊不清的感觉,但你并不需要这样做,因为Visual Basic .NET编译器将会为你做好这个工作。
在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的类并且它包含附加信息。你可以使用你的类的构造器来接受并存储信息。在示例项目中,FileFoundEventArgs类如图 8 所示。
第二,提供一个引发事件的过程。大多.NET Framework类从一个重载的protected过程引发事件,一般命名为OnEventName(在FileFound事件的情况下,过程会被命名为OnFileFound)。需要引发事件的代码调用OnEventName过程,它将接着引发事件。使之成为一个protected方法意味着它可为当前类型的对象所用和基于继承自当前类的任何对象所用。使之重载意味着继承类可以改变事件的行为: 一个继承类可以添加运行于调用基类的OnEventName过程之前或之后的代码,或者可以全部跳过它们。在这个示例项目中,FileSearch5类提供以下protected过程:
Public Class FileFoundEventArgs
Inherits EventArgs
Private mfi As FileInfo
Public ReadOnly Property FileFound() As FileInfo
Get
Return mfi
End Get
End Property
Public Sub New(ByVal fi As FileInfo)
’ Store the FileInfo object for later use.
mfi = fi
End Sub
End Class
Protected Overridable Sub OnFileFound(ByVal fi As FileInfo)
RaiseEvent FileFound(Me, New FileFoundEventArgs(fi))
End Sub
第三,你可能发现创建你自己的事件委托是很有用的。尽管你无须定义一个显式事件委托就可获得事件委托,但在自己创建时可获得一些灵活性。在你创建一个委托时,你正在为过程定义一个“类型”。如果你有不止一个事件,它们需要同一套参数,创建一个定义这个类型的委托将会有用。如果你需要修改这些参数,你可以只要修改委托,而不用修改事件声明。
举个例子,你可以声明这个FileFound事件,不用事件委托,如下:
ByVal sender As Object, ByVal e As FileFoundEventArgs)
ByVal sender As Object, ByVal e As FileFoundEventArgs)
ByVal sender As Object, ByVal e As FileFoundEventArgs)
Public Event FileFoundSomeOtherEvent As FileFoundEventHandler
Dim afi() As FileInfo = diLocal.GetFiles(Me.FileSpec)
For Each fi As FileInfo In afi
OnFileFound(fi)
Next
Private Sub fs5_FileFound( _
ByVal sender As Object, ByVal e As FileFoundEventArgs) _
Handles fs5.FileFound
AddText(e.FileFound.FullName)
End Sub
