使用事件编程 由于在.NET中的事件建立在委托的顶层,所有它们下面的通道细节与早期版本的Visual Basic工作方式有很大的不同。但是Visual Basic .NET语言的设计者为了保持与早期Visual Basic版本语言的一致性做了大量的工作。在很多情况中,事件编程使用与原来相近的语法。例如,你将使用Event、 RaiseEvent和WithEvents等关键字,它们的行为与早期版本的相同。
我们建立一个简单的基于事件的回调设计。首先我需要使用Event关键字在类的定义中定义一个事件。事件必须根据特定的委托类型来定义。下面是一个委托类型定义和使用它定义事件的类:
Delegate Sub LargeWithdrawHandler(ByVal Amount As Decimal)
Class BankAccount Public Event LargeWithdraw As LargeWithdrawHandler '省略了其它成员 End Class |
在上面的例子中,LargeWithdraw事件被定义为一个实例成员。该设计中BankAccount对象将作为事件源。如果你希望用类代替对象作为事件源,你可以使用Shared关键字把事件定义为共享成员。
当使用事件编程时,肯定编译器在后台做了大量的工作也很重要。例如,当编译BankAccount类的定义时编译器做了什么?图2显示了使用ILDasm.exe(中间语言反编译器)查看类的定义结果。
图2. ILDasm中的类定义
当你定义事件时,编译器在类的定义中产生四个成员。第一个成员是基于委托类型的私有字段,它用于跟踪一个委托对象的引用。编译器产生的该私有字段的名字是事件的名字加上"Event"标识。这意味着建立LargeWithdraw事件的结果是建立了名为LargeWithdrawEvent的私有字段。
编译器也产生了两个方法帮助注册和取消注册作为事件处理程序服务的委托对象。这两个方法的命名使用了标准的命名转换。注册事件处理程序的方法的名字加了"add_"前缀,取消注册的方法前面加了"remove_"前缀。因此为LargeWithdraw事件建立的这两个方法名称为add_LargeWithdraw 和remove_LargeWithdraw。
Visual Basic .NET编译器为add_LargeWithdraw产生了实现代码,它接收以一个委托对象作为参数并通过调用委托类的Combine方法将它添加到处理程序列表。编译器也产生remove_LargeWithdraw的实现代码,它通过调用委托类的Remove方法从列表中删除一个处理方法。
添加到类定义中的第四个成员表现了事件本身。你能在图2中定位名为LargeWithdraw的事件成员。它有一个向下的三角形。但是你必须注意这个事件成员不是一个真的与其它三个相似的物理事件,它是一个元数据成员。
该元数据事件成员是有价值的,因为它通知编译器和其它开发工具该类支持.NET框架中的事件注册标准模式。该事件成员也包含注册和反注册方法的名字。这使Visual Basic .NET和C#等可管理语言的编译器能在编译时就发现注册方法的名称。
当Visual Basic .NET发现类定义中包含事件时,它自动在产生事件处理程序的注册代码时生成该处理方法的框架定义。
在讨论引发事件前,我将讲解建立用于定义事件的委托类型所涉及的限制。定义事件的委托类型不能有返回值,你必须使用Sub关键字而不能使用Function关键字:
'能被事件使用 Delegate Sub BaggageHandler() Delegate Sub MailHandler(ItemID As Integer)
'不能被事件使用 Delegate Function QuoteOfTheDayHandler(Funny As Boolean) As String |
该限制有一个很好的原因。在绑定到多个处理方法的多点传送委托中使用返回值非常困难。多点传送委托的Invoke调用返回的值是调用列表中最后一个处理方法的值。可是捕获列表中前面的处理方法的返回值就不直接了,消除捕获多个返回值的需求使事件更容易使用。