Windows 运行时组件中的自定义事件和事件访问器
对 Windows 运行时组件的 .NET 支持使声明事件组件变得简单,方法是通过隐藏通用 Windows 平台 (UWP) 事件模式和 .NET 事件模式之间的差异。 但在 Windows 运行时组件中声明自定义事件访问器时,必须遵循在 UWP 中使用的模式。
正在注册事件
当你注册以在 UWP 中处理事件时,添加访问器将返回令牌。 若要取消注册,请将此令牌传递到删除访问器。 这意味着 UWP 事件的添加和删除访问器具有不同于你习惯使用的访问器的签名。
幸运的是,Visual Basic 和 C# 编译器简化了此过程:当在 Windows 运行时组件中通过自定义访问器声明事件时,编译器将自动使用 UWP 模式。 例如,如果添加访问器没有返回令牌,你将收到编译器错误。 .NET 提供两种类型来支持实现:
- EventRegistrationToken 结构表示令牌。
- EventRegistrationTokenTable<T> 类创建令牌并维护令牌和事件处理程序之间的映射。 泛型类型参数是事件参数类型。 你为每个事件创建此类的实例,并且首次为该事件注册事件处理程序。
NumberChanged 事件的以下代码显示了 UWP 事件的基本模式。 在本示例中,事件参数对象 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
静态(在 Visual Basic 中为 Shared)GetOrCreateEventRegistrationTokenTable 方法延迟创建 EventRegistrationTokenTable<T> 对象的事件实例。 将保留令牌表实例的类级别字段传递到此方法。 如果该字段为空,该方法会创建表、在字段中存储对表的引用并返回对表的引用。 如果该字段已经包含令牌表引用,该方法将仅返回该引用。
重要提示 若要确保线程安全,保留 EventRegistrationTokenTable<T> 的事件实例的字段必须是类级别字段。 如果它是类级别字段,GetOrCreateEventRegistrationTokenTable 方法会确保在多个线程尝试创建令牌表时,所有线程均会获取相同的表实例。 对于指定事件,对 GetOrCreateEventRegistrationTokenTable 方法的所有调用都必须使用相同的类级别字段。
在删除访问器和 RaiseEvent 方法(在 C# 中是 OnRaiseEvent 方法)中调用 GetOrCreateEventRegistrationTokenTable 方法可确保在以下情况下不会发生任何异常:在添加任何事件处理程序委托之前调用这些方法。
其他在 UWP 事件模式中使用的 EventRegistrationTokenTable<T> 类的成员包括:
AddEventHandler 方法生成事件处理程序委托的令牌、在表中存储委托、将其添加到调用列表以及返回该令牌。
RemoveEventHandler(EventRegistrationToken) 方法重载会从表和调用列表中删除委托。
注意 AddEventHandler 和 RemoveEventHandler(EventRegistrationToken) 方法锁定表以帮助确保线程安全。
InvocationList 属性返回包括所有事件处理程序的委托,并且这些事件处理程序当前已注册为处理该事件。 使用此委派引发事件,或使用委派类的方法单独调用处理程序。
注意 我们建议你遵循在本文的前面部分中提供的示例所示的模式,并在调用委托前将其复制到临时变量。 这可避免一个线程删除最后的处理程序的争用条件,从而在其他线程尝试调用委派之前将其减少为 null。 委派不可变动,因此副本仍然有效。
根据情况将自己的代码置于访问器中。 如果线程安全出现问题,必须自行锁定代码。
C# 用户:当你在 UWP 事件模式中编写自定义事件访问器时,编译器将不会提供常用的语法快捷方式。 如果你使用代码中的事件的名称,它将生成错误。
Visual Basic 用户:在 .NET 中,事件仅是表示所有注册的事件处理程序的多播委派。 引发事件即表明调用委派。 Visual Basic 语法通常隐藏与委派的交互,而编译器会在调用委派前复制它,如有关线程安全的注释中所述。 当你在 Windows 运行时组件中创建自定义事件时,必须直接处理委派。 这也意味着你可以使用 MulticastDelegate.GetInvocationList 方法(例如)为每个事件处理程序获取包含独立委托的数组,条件是你想要单独调用处理程序。