事件是你的代码兵器库中的主要部分,无论你用Visual Basic? 6.0,Visual Basic .NET 2002,Visual Basic .NET 2003,还是Visual Basic 2005 。
研究异常(Exception)行为
我将很快给你看看从FileSearch3类中选取的代码。每次一个类的实例找到一个文件,实例就引发它的FileFound事件。这一效果导致.NET Framework相继地运行每个事件处理过程。看看下面的代码:
’’以下代码来自FileSearch3.vb ’’搜索符合的文件名 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) |
这个技术的问题(正如你已经看到的)是:如果一个异常出现在任何一个侦听器中,异常回滚(bubbles back)到事件引发(event-raising)代码,.NET Framework不再调用事件侦听器,而事件处理就慢慢停了下来。
如果查看一下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方法包括独立地调用每个侦听器的代码,它也可以为每个调用处理异常。
’ From FileSearch4.vb 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
|
FileSearch4.Search中的新代码采取了以下动作:
* 声明一个System.Delegate类型的数组以使得代码可以追踪到事件的所有侦听器:
Dim ListenerList() As System.Delegate |
* 声明一个描述事件的委托类型的实例,以遍历侦听器数组(你应该记得:所有侦听器过程必须有其特定的类型,否则这个代码将不能被编译,这就是委托的工作方法):
Dim Listener As FileFoundEventHandler |
* 重新获得侦听器列表,调用FileFoundEvent委托的内部GetInvocationList方法:
ListenerList = FileFoundEvent.GetInvocationList() |
* 找到文件并遍历包含cor-responding FileInfos的数组,正如你前面已经看到的一样:
Dim afi() As FileInfo = diLocal.GetFiles(Me.FileSpec)
For Each fi As FileInfo In afi ’’ Code removed here... Next |
* 每找到一个文件,FileSearch4.Search就遍历事件侦听器的列表并独立地调用每个委托的回调(Invoke)方法。这允许代码捕捉(而且既然这样,就忽略)任何由每个独立地的侦听器引发的异常。
For Each Listener In ListenerList 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停止为当前事件调用侦听器)。为了这样做,确保你自己的事件处理不允许回滚。如果你希望通过多个事件过程获得一个单独的事件处理的话,在你的事件过程中处理所有异常以使得你不会打破事件处理程序的链条。