分享
 
 
 

使用.NET对事件进行编程

王朝c#·作者佚名  2008-05-19
窄屏简体版  字體: |||超大  

您可能已经对事件进行编程若干年了,但是迁移到 .NET Framework 仍然需要您重新检查事件的内部工作,因为 .NET Framework 中的事件位于委托的顶层。 对委托的了解越多,对事件进行编程时所具有的驾驭能力越强。 开始使用公共语言运行库 (CLR) 的某个事件驱动框架(例如 Windows? Forms 或 ASP.NET)时,理解事件在较低的级别如何工作至关重要。 本月我的目标是使您理解事件在较低的级别如何工作。

什么是事件?

事件是一种形式化的软件模式,在该模式中,通知源将对一个或多个处理程序方法进行回调。 因此,事件类似于接口和委托,因为它们提供了设计使用回调方法的应用程序的方法。 但是,事件极大地提高了工作效率,因为它们使用起来比接口或委托更容易。 事件允许编译器和 Visual Studio? .NET IDE 在幕后为您做大量的工作。

涉及事件的设计基于事件源和一个或多个事件处理程序。 事件源可以是一个类也可以是一个对象。 事件处理程序是绑定到处理程序方法的委托对象。 图 1 显示了绑定到其处理程序方法的事件源的高级别视图。

图 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 编译器通过调用 Delegate 类的 Combine 方法为将委托对象作为参数接受并将其添加到处理程序列表中的 add_LargeWithdraw 生成一个实现。 编译器通过在 Delegate 类中调用 Remove 方法为从列表中删除一个处理程序方法的 remove_LargeWithdraw 生成一个实现。

第四个也是最后一个添加到类定义中的成员是表示事件本身的成员。 在图 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 关键字与在以下 snippet 中编写代码具有完全相同的效果:

If (Not LargeWithdrawEvent Is Nothing) Then

LargeWithdrawEvent.Invoke(Amount)

End If

注意,Visual Basic .NET 编译器生成的代码执行检查以确保 LargeWithdrawEvent 字段包含对某个对象的有效引用。 这是因为 LargeWithdrawEvent 字段的值在第一个处理程序方法注册之前一直为 Nothing。 因此,除非当前至少有一个处理程序方法已注册,否则生成的代码并不尝试调用 Invoke。

您应该能够对激发事件进行观察。 使用 RaiseEvent 关键字或者根据编译器自动生成的 LargeWithdrawEvent 私有字段直接进行编程通常并没有什么分别。 两种方法都生成相同的代码:

'*** this code

RaiseEvent LargeWithdraw(Amount)

'*** is the same as this code

If (Not LargeWithdrawEvent Is Nothing) Then

LargeWithdrawEvent.Invoke(Amount)

End If

在很多情况下,您可能喜欢使用 RaiseEvent 关键字语法,因为它需要的键入较少,生成的代码较简洁。 但是,在某些情况下,当您需要较多控制时,根据 LargeWithdrawEvent 私有字段进行明确编程可能会有意义。 让我们看一个这种情况的示例。

想象以下情况:BankAccount 对象有三个事件处理程

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有