当前位置:编程学习 > C#/ASP.NET >>

使用 .NET 对事件进行编程

答案:     本月的“基本功能”专栏建立在本人上两期专栏的基础之上,在上两期“基本功能”专栏中,我讨论了与委托相关的概念和编程技巧。本文假定读者已经阅读了该专栏的上两期,并且理解委托在 Microsoft®.NET Framework 中所扮演的角色。如果您尚未阅读上两期专栏,请参阅 Implementing Callback Notifications Using Delegates 和 Implementing Callbacks with a Multicast Delegate。您还应该知道如何设计和编写使用多路广播委托将回调通知发送到一组处理程序方法的简单应用程序。
  
  您可能已经对事件进行编程若干年了,但是迁移到 .NET Framework 仍然需要您重新检查事件的内部工作,因为 .NET Framework 中的事件位于委托的顶层。对委托了解得越多,对事件进行编程时所具有的驾驭能力就越强。在开始使用公共语言运行库 (CLR) 中的一个事件驱动框架(例如 Windows® Forms 或 ASP.NET)时,理解事件在较低的级别如何工作至关重要。本月,我的目标是使您理解事件在较低的级别如何工作。
  
  事件究竟是什么?
  
  事件只是一种形式化的软件模式,在该模式中,通知源对一个或多个处理程序方法进行回调。因此,事件类似于接口和委托,因为它们提供了设计使用回调方法的应用程序的方法。但是,事件大大提高了工作效率,因为它们使用起来比接口或委托更容易。事件允许编译器和 Visual Studio®.NET IDE 在幕后为您做大量的工作。
  
  涉及事件的设计基于事件源和一个或多个事件处理程序。事件源可以是一个类也可以是一个对象。事件处理程序是绑定到处理程序方法的委托对象。图 1 显示了一个绑定到其处理程序方法的事件源的高级别视图。
  [img]/upload/2006-3/2006313222111672.gif[/img
  图 1 事件源和处理程序
  
  
  每个事件都是根据特定的委托类型定义的。对于事件源定义的每个事件,都有一个基于事件的基础委托类型的私有字段。该字段用于跟踪多路广播委托对象。事件源还提供允许您注册所需数量的事件处理程序的公共注册方法。
  
  当您创建事件处理程序(委托对象)并在某个事件源中注册它时,该事件源只是将新的事件处理程序追加到列表末尾。然后,事件源可以使用私有字段在多路广播委托上调用 Invoke,该多路广播委托将依次执行所有已注册的事件处理程序。
  
  事件的真正好处在于,对其进行的大量设置工作已经为您做好了。正如您很快就会看到的那样,无论何时您定义事件,Visual Basic®.NET 编译器都会通过自动添加私有委托字段和公共注册方法来协助您工作。您还将看到 Visual Studio .NET 可以通过一个代码生成器提供更多的帮助,该代码生成器可以自动发出适用于您的处理程序方法的主干定义。
  
  对事件进行编程
  
  由于 .NET 中的事件建立在委托之上,因此它们的基础结构详细信息与在早期版本的 Visual Basic 中使用事物的方式截然不同。但是,Visual Basic .NET 的语言设计者在保持事件编程的语法与早期版本的 Visual Basic 相一致方面做得很好。在很多情况下,对事件进行编程会涉及到您习惯使用的熟悉的旧语法。例如,您将使用 Event、RaiseEvent 和 WithEvents 等关键字,而它们的行为方式与其在早期版本的 Visual Basic 中的行为方式几乎完全相同。
  
  下面让我们先创建一个基于事件的简单回调设计。首先,我需要使用 Event 关键字在类定义中定义一个事件。必须根据特定的委托类型定义每个事件。下面是定义自定义委托类型和用来定义事件的类的一个示例:
  
  Delegate Sub LargeWithdrawHandler(ByVal Amount As Decimal)
  
  Class BankAccount
   Public Event LargeWithdraw As LargeWithdrawHandler
   '*** other members omitted
  End Class
  
  在本示例中,LargeWithdraw 事件被定义为实例成员。在本设计中,BankAccount 对象将充当事件源。如果您希望类(而不是对象)充当事件源,则应该使用 Shared 关键字将事件定义为共享成员。
  
  对事件进行编程时,知道编译器在幕后为您做了大量额外工作这一点很重要。例如,当您将刚才所示的 BankAccount 类的定义编译到程序集时,您认为编译器会做什么?图 2 显示了用中间语言反汇编程序 ILDasm.exe 检查生成的类定义时,该定义的样子。该视图毫无保留地向您显示了 Visual Basic .NET 编译器在幕后做了多少工作来帮助您。
  
  图 2 ILDasm 中的类定义
  
  
  在您定义事件时,编译器会在类定义内生成四个成员。第一个成员是基于委托类型的私有字段。该字段用于跟踪对委托对象的引用。该编译器通过采用事件本身的名称并添加后缀“Event”来生成该私有字段的名称。这意味着,创建名为 LargeWithdraw 的事件将导致创建名为 LargeWithdrawEvent 的私有字段。
  
  该编译器还会生成两个方法,以帮助注册和注销要用作事件处理程序的委托对象。这两个方法都使用标准命名约定进行命名。用于注册事件处理程序的方法通过在事件名称前添加前缀“add_”来命名。用于注销事件处理程序的方法通过在事件名称前添加前缀“remove_”来命名。因此,为 LargeWithdraw 事件创建的两个方法名为 add_LargeWithdraw 和 remove_LargeWithdraw。
  
  Visual Basic .NET 编译器为 add_LargeWithdraw 生成一个实现,该实现将委托对象作为参数接受,并通过调用 Delegate 类的 Combine 方法将委托对象添加到处理程序的列表中。该编译器还为 remove_LargeWithdraw 生成一个实现,该实现通过调用 Delegate 类中的 Remove 方法从列表中删除一个处理程序方法。
  
  第四个也是最后一个添加到类定义中的成员是表示事件本身的成员。在图 2中,您应该能够找到名为 LargeWithdraw 的事件成员。该成员旁边带有一个倒三角。但是,您应该注意到,该事件成员并不像其它三个那样是一个实际的物理成员。相反,它是一个仅包含元数据的成员。
  
  这个仅包含元数据的事件成员很有价值,因为它可以通知编译器和其他开发工具该类支持 .NET Framework 中事件注册的标准模式。该事件成员还包含注册方法和注销方法的名称。这使得 Visual Basic .NET 和 C# 等托管语言的编译器能够在编译时发现注册方法的名称。
  
  Visual Studio .NET 是查找这个仅包含元数据的事件成员的开发工具的另一个很好的示例。当 Visual Studio .NET 发现类定义包含事件时,它将自动生成处理程序方法的主干定义,以及将它们作为事件处理程序进行注册的代码。
  
  在开始讨论引发事件之前,我想提出一个与创建要用于定义事件的委托类型有关的限制。用于定义事件的委托类型不能有返回值。您必须使用 Sub 关键字(而不是 Function 关键字)来定义委托类型,如下所示:
  
  '*** can be used for events
  Delegate Sub BaggageHandler()
  Delegate Sub MailHandler(ItemID As Integer)
  
  '*** cannot be used for events
  Delegate Function QuoteOfTheDayHandler(Funny As Boolean) As String
  
  此限制有很充分的原因。当涉及与若干处理程序方法绑定的多路广播委托时,处理返回值相当困难。在多路广播委托上调用 Invoke 将返回与调用列表中最后一个处理程序方法相同的值。但是,捕获先前在列表中出现的处理程序方法的返回值并不是那么简单。消除对捕获多个返回值的需要只会使事件更加易于使用。
  
  引发事件
  
  现在,让我们修改 BankAccount 类,使其能够在提款数额超出 $5000 阈值时引发一个事件。引发 LargeWithdraw 事件的最简单方法是,在一个方法、属性或构造函数的实现中使用 RaiseEvent 关键字。您可能会觉得该语法很熟悉,因为它类似于您在早期版本的 Visual Basic 中使用的语法。下面是从 Withdraw 方法引发 LargeWithdraw 事件的一个示例:
  
  Class BankAccount
   Public Event LargeWithdraw As LargeWithdrawHandler
   Sub Withdraw(ByVal Amount As Decimal)
   '*** send notifications if required
   If (Amount > 5000) Then
   RaiseEvent LargeWithdraw(Amount)
   End If
   '*** perform withdrawal
   End Sub
  End Class
  
  虽然该语法与早期版本的 Visual Basic 相同,但是现在引发事件时所发生的事情则截然不同。在您使用 RaiseEvent 关键字引发事件时,Visual Basic .NET 编译器会生成执行每个事件处理程序所需的代码。例如,在编译以下代码时,您认为会发生什么事情?
  
  RaiseEvent LargeWithdraw(Amount)
  
  Visual Basic .NET 编译器将该表达式扩展为在保留多路广播委托对象的私有字段上调用 Invoke 的代码。换句话说,使用 RaiseEvent 关键字与编写以下代

上一个:利用Visual C#实现Window管道技术
下一个:在 C# 中加载自己编写的动态链接库

CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,