引发事件
现在我们修改BankAccount类,使取款总额超过5000元限制时产生一个事件。引发LargeWithdraw事件的最简单途径使在某个方法、属性或构造函数的执行中使用RaiseEvent关键字。该语法与Visual Basic早期版本的语法相似,下面的代码从Withdraw方法中引发LargeWithdraw事件:
Class BankAccount Public Event LargeWithdraw As LargeWithdrawHandler Sub Withdraw(ByVal Amount As Decimal) '如果需要的话就发送通知 If (Amount > 5000) Then RaiseEvent LargeWithdraw(Amount) End If '执行撤消 End Sub End Class |
尽管语法与Visual Basic早期版本相同,但是引发事件时发生的事情却不同了。当你使用RaiseEvent关键字引发一个事件时,Visual Basic .NET编译器产生必要的代码来运行每个事件处理程序。例如,你知道编译下面的代码时会发生什么吗?
RaiseEvent LargeWithdraw(Amount) |
Visual Basic .NET编译器扩充表达式成为代码,代码调用保存多点传送委托对象的私有字段上的Invoke方法。就是说,使用RaiseEvent关键字的效果与下面的代码相同:
If (Not LargeWithdrawEvent Is Nothing) Then LargeWithdrawEvent.Invoke(Amount) End If |
注意Visual Basic .NET编译器产生的代码实施一个检查以确保LargeWithdrawEvent字段包含一个有效的对象引用。这是因为LargeWithdrawEvent字段的值将为空,直到第一个处理方法被注册。因此,产生的代码不会试图调用Invoke,除非至少有一个处理方法被注册了。
你能够观察事件的引发。无论你使用RaiseEvent关键字或者程序中直接访问编译器自动产生的LargeWithdrawEvent字段都没有关系。两种访问产生相同的代码:
'这段代码 RaiseEvent LargeWithdraw(Amount)
'与下面的代码相同 If (Not LargeWithdrawEvent Is Nothing) Then LargeWithdrawEvent.Invoke(Amount) End If |
大多数情况下使用RaiseEvent关键字,因为这种语法的输入少、代码简洁。但是当你需要更多控制时,可能会使用到LargeWithdrawEvent字段的显式调用。
假设BankAccount对象有三个已经注册的事件处理程序用于接收LargeWithdraw事件的通知。如果你使用RaiseEvent关键字触发事件并且调用列表中的第二个事件处理程序出现了错误会发生什么事情呢?包含RaiseEvent语句的代码行将收到一个运行时错误,但是你没有办法调试是哪一个事件处理程序产生的。另外,第二个事件处理方法产生的异常没有办法处理并按希望继续运行第三个事件处理程序。
Sub Withdraw(ByVal Amount As Decimal) '如果需要的话就发送通知 If (Amount > 5000) AndAslo (Not LargeWithdrawEvent Is Nothing) Then Dim handler As LargeWithdrawHandler For Each handler In LargeWithdrawEvent.GetInvocationList() Try handler.Invoke(Amount) Catch ex As Exception '处理异常 End Try Next End If '执行撤消 End Sub |
图3.使用私有委托字段
但是如果编程访问私有的LargeWithdrawEvent字段,你就可以使用更好的方式处理一个事件处理方法产生的异常。查看图3中的代码,你会发现,处理较低层次并且对私有委托字段编程提供了额外的控制能力。你可以轻易地处理异常并继续执行列表后面地事件处理程序。这种技术比RaiseEvent语法明显的好处,RaiseEvent语法中事件处理方法产生的异常阻止了列表后面的其它事件处理程序的执行。