Windows 运行时组件中的自定义事件和事件访问器
.NET Framework 支持 Windows 运行时,因此很容易在 Windows 运行时组件中声明事件,声明时只需隐藏 Windows 运行时事件模式与 .NET Framework 事件模式之间的差别即可。但是,在 Windows 运行时组件中声明自定义事件访问器时,必须遵循 Windows 运行时模式。
当你在 Windows 运行时中注册以便处理事件时,add 访问器会返回一个标记。若要注销,请将此标记传递给 remove 访问器。这意味着,用于 Windows 运行时事件的 add 和 remove 访问器的签名不同于你所习惯的访问器。
幸运的是,Visual Basic 和 C# 编译器简化了这一过程:在 Windows 运行时组件中使用自定义访问器声明事件时,编译器自动使用 Windows 运行时模式。例如,如果 add 访问器不返回标记,则会获得编译器错误。.NET Framework 提供了两个类型来支持实现:
EventRegistrationToken 结构表示标记。
EventRegistrationTokenTable<T> 类创建标记并维护标记与事件处理程序之间的映射。泛型类型参数是事件参数类型。第一次为每个事件注册事件处理程序时,可为该事件创建一个此类的实例。
以下用于 NumberChanged 事件的代码显示了 Windows 运行时事件的基本模式。在此示例中,事件参数对象 NumberChangedEventArgs 的构造函数采用表示已更改数值的单个整数参数。
备注
此模式与编译器用于普通事件的模式相同,普通事件是在 Windows 运行时组件中声明的。
private EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
m_NumberChangedTokenTable = null;
public event EventHandler<NumberChangedEventArgs> NumberChanged
{
add
{
return EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.AddEventHandler(value);
}
remove
{
EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.RemoveEventHandler(value);
}
}
internal void OnNumberChanged(int newValue)
{
EventHandler<NumberChangedEventArgs> temp =
EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.InvocationList;
if (temp != null)
{
temp(this, new NumberChangedEventArgs(newValue));
}
}
Private m_NumberChangedTokenTable As _
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs))
Public Custom Event NumberChanged As EventHandler(Of NumberChangedEventArgs)
AddHandler(ByVal handler As EventHandler(Of NumberChangedEventArgs))
Return EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
AddEventHandler(handler)
End AddHandler
RemoveHandler(ByVal token As EventRegistrationToken)
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
RemoveEventHandler(token)
End RemoveHandler
RaiseEvent(ByVal sender As Class1, ByVal args As NumberChangedEventArgs)
Dim temp As EventHandler(Of NumberChangedEventArgs) = _
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
InvocationList
If temp IsNot Nothing Then
temp(sender, args)
End If
End RaiseEvent
End Event
static(在 Visual Basic 中为 Shared)GetOrCreateEventRegistrationTokenTable 方法惰性创建事件的 EventRegistrationTokenTable<T> 对象的实例。将保存标记表实例的类级别字段传递给此方法。如果该字段为空,方法将创建表,在该字段中存储对表的引用,并返回对表的引用。如果字段已包含标记表引用,此方法只返回该引用。
重要
若要确保线程安全,保存事件的 EventRegistrationTokenTable<T> 实例的字段必须为类级别字段。如果它是类级别字段,GetOrCreateEventRegistrationTokenTable 方法可确保当多个线程尝试创建标记表时,所有线程会获取同一个表实例。对于给定事件,对 GetOrCreateEventRegistrationTokenTable 方法的所有调用必须使用同一类级别字段。
在 remove 访问器和 RaiseEvent 方法(在 C# 中为 OnRaiseEvent 方法)中调用 GetOrCreateEventRegistrationTokenTable 方法可确保不会发生异常,前提是在添加任何事件处理程序委托之前调用这些方法。
用在 Windows 运行时事件模式中的 EventRegistrationTokenTable<T> 类的其他成员包括:
AddEventHandler 方法为事件处理程序委托生成一个标记,在表中存储该委托,然后将其添加到调用列表中并返回该标记。
RemoveEventHandler(EventRegistrationToken) 方法重载从表和调用列表中删除委托。
备注
AddEventHandler 和 RemoveEventHandler(EventRegistrationToken) 方法锁定表,以帮助确保线程安全。
InvocationList 属性返回一个委托,其中包括当前注册用于处理事件的所有事件处理程序。使用此委托引发事件,或使用 Delegate 类的方法逐个调用处理程序。
备注
建议你遵循本文前面提供的示例中显示的模式,并在调用委托之前将其复制到一个临时变量中。这样可以避免出现争用情况:一个线程移除最后一个处理程序,在其他线程尝试调用委托之前将委托减少至 null。委托是不可变的,因此副本仍然有效。
酌情将你自己的代码放在访问器中。如果线程安全是个问题,则必须为代码提供锁定。
C# 用户:在 Windows 运行时事件模式中编写自定义事件访问器时,编译器不提供常用的语法快捷方式。如果在代码中使用事件的名称,则会生成错误。
Visual Basic 用户:在 .NET Framework 中,事件只是表示所有已注册事件处理程序的多播委托。引发事件仅意味着调用该委托。Visual Basic 语法通常会隐藏与委托的交互,编译器在调用委托之前会复制委托,如有关线程安全的说明中所述。在 Windows 运行时组件中创建自定义事件时,必须直接处理委托。这也意味着,如果要分别调用处理程序,则可使用 MulticastDelegate.GetInvocationList 之类的方法为每个事件处理程序获取包含单独委托的数组。