Windows 运行时组件中的自定义事件和事件访问器Custom events and event accessors in Windows Runtime components

.NET 对 Windows 运行时组件的支持使你可以轻松地通过隐藏 (UWP) 事件模式和 .NET 事件模式的通用 Windows 平台之间的差异来声明事件组件。.NET support for Windows Runtime components makes it easy to declare events components by hiding the differences between the Universal Windows Platform (UWP) event pattern and the .NET event pattern. 但是,在 Windows 运行时组件中声明自定义事件访问器时,必须遵循 UWP 中使用的模式。However, when you declare custom event accessors in a Windows Runtime component, you must follow the pattern used in the UWP.

正在注册事件Registering events

当你注册以在 UWP 中处理事件时,添加访问器将返回令牌。When you register to handle an event in the UWP, the add accessor returns a token. 若要取消注册,请将此令牌传递到删除访问器。To unregister, you pass this token to the remove accessor. 这意味着 UWP 事件的添加和删除访问器具有不同于你习惯使用的访问器的签名。This means that the add and remove accessors for UWP events have different signatures from the accessors you're used to.

幸运的是,Visual Basic 和 c # 编译器简化了此过程:在 Windows 运行时组件中使用自定义访问器声明事件时,编译器会自动使用 UWP 模式。Fortunately, the Visual Basic and C# compilers simplify this process: When you declare an event with custom accessors in a Windows Runtime component, the compilers automatically use the UWP pattern. 例如,如果添加访问器没有返回令牌,你将收到编译器错误。For example, you get a compiler error if your add accessor doesn't return a token. .NET 提供了两种支持实现的类型:.NET provides two types to support the implementation:

  • EventRegistrationToken 结构表示令牌。The EventRegistrationToken structure represents the token.
  • EventRegistrationTokenTable<T> 类创建令牌并维护令牌和事件处理程序之间的映射。The EventRegistrationTokenTable<T> class creates tokens and maintains a mapping between tokens and event handlers. 泛型类型参数是事件参数类型。The generic type argument is the event argument type. 你为每个事件创建此类的实例,并且首次为该事件注册事件处理程序。You create an instance of this class for each event, the first time an event handler is registered for that event.

NumberChanged 事件的以下代码显示了 UWP 事件的基本模式。The following code for the NumberChanged event shows the basic pattern for UWP events. 在本示例中,事件参数对象 NumberChangedEventArgs 的构造函数接受代表更改的数字值的单精度整数参数。In this example, the constructor for the event argument object, NumberChangedEventArgs, takes a single integer parameter that represents the changed numeric value.

注意   此模式与编译器用于在 Windows 运行时组件中声明的普通事件的模式相同。Note  This is the same pattern the compilers use for ordinary events that you declare in a Windows Runtime component.

 

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> 对象的事件实例。The static (Shared in Visual Basic) GetOrCreateEventRegistrationTokenTable method creates the event’s instance of the EventRegistrationTokenTable<T> object lazily. 将保留令牌表实例的类级别字段传递到此方法。Pass the class-level field that will hold the token table instance to this method. 如果该字段为空,该方法会创建表、在字段中存储对表的引用并返回对表的引用。If the field is empty, the method creates the table, stores a reference to the table in the field, and returns a reference to the table. 如果该字段已经包含令牌表引用,该方法将仅返回该引用。If the field already contains a token table reference, the method just returns that reference.

重要提示   为了确保线程安全,保存 EventRegistrationTokenTable T 的事件实例的字段 < > 必须是类级别的字段。Important  To ensure thread safety, the field that holds the event’s instance of EventRegistrationTokenTable<T> must be a class-level field. 如果它是类级别字段,GetOrCreateEventRegistrationTokenTable 方法会确保在多个线程尝试创建令牌表时,所有线程均会获取相同的表实例。If it is a class-level field, the GetOrCreateEventRegistrationTokenTable method ensures that when multiple threads try to create the token table, all threads get the same instance of the table. 对于指定事件,对 GetOrCreateEventRegistrationTokenTable 方法的所有调用都必须使用相同的类级别字段。For a given event, all calls to the GetOrCreateEventRegistrationTokenTable method must use the same class-level field.

在删除访问器和 RaiseEvent 方法(在 C# 中是 OnRaiseEvent 方法)中调用 GetOrCreateEventRegistrationTokenTable 方法可确保在以下情况下不会发生任何异常:在添加任何事件处理程序委托之前调用这些方法。Calling the GetOrCreateEventRegistrationTokenTable method in the remove accessor and in the RaiseEvent method (the OnRaiseEvent method in C#) ensures that no exceptions occur if these methods are called before any event handler delegates have been added.

其他在 UWP 事件模式中使用的 EventRegistrationTokenTable<T> 类的成员包括:The other members of the EventRegistrationTokenTable<T> class that are used in the UWP event pattern include the following:

  • AddEventHandler 方法生成事件处理程序委托的令牌、在表中存储委托、将其添加到调用列表以及返回该令牌。The AddEventHandler method generates a token for the event handler delegate, stores the delegate in the table, adds it to the invocation list, and returns the token.

  • RemoveEventHandler(EventRegistrationToken) 方法重载会从表和调用列表中删除委托。The RemoveEventHandler(EventRegistrationToken) method overload removes the delegate from the table and from the invocation list.

    注意   AddEventHandler 和 RemoveEventHandler (EventRegistrationToken) 方法会锁定表,以帮助确保线程安全。Note  The AddEventHandler and RemoveEventHandler(EventRegistrationToken) methods lock the table to help ensure thread safety.

  • InvocationList 属性返回包括所有事件处理程序的委托,并且这些事件处理程序当前已注册为处理该事件。The InvocationList property returns a delegate that includes all the event handlers that are currently registered to handle the event. 使用此委派引发事件,或使用委派类的方法单独调用处理程序。Use this delegate to raise the event, or use the methods of the Delegate class to invoke the handlers individually.

    注意   建议遵循本文前面提供的示例中所示的模式,并在调用前将委托复制到临时变量。Note  We recommend that you follow the pattern shown in the example provided earlier in this article, and copy the delegate to a temporary variable before invoking it. 这可避免一个线程删除最后的处理程序的争用条件,从而在其他线程尝试调用委派之前将其减少为 null。This avoids a race condition in which one thread removes the last handler, reducing the delegate to null just before another thread tries to invoke the delegate. 委派不可变动,因此副本仍然有效。Delegates are immutable, so the copy is still valid.

根据情况将自己的代码置于访问器中。Place your own code in the accessors as appropriate. 如果线程安全出现问题,必须自行锁定代码。If thread safety is an issue, you must provide your own locking for your code.

C# 用户:当你在 UWP 事件模式中编写自定义事件访问器时,编译器将不会提供常用的语法快捷方式。C# users: When you write custom event accessors in the UWP event pattern, the compiler doesn't provide the usual syntactic shortcuts. 如果你使用代码中的事件的名称,它将生成错误。It generates errors if you use the name of the event in your code.

Visual Basic 用户:在 .NET 中,事件只是表示所有已注册事件处理程序的多路广播委托。Visual Basic users: In .NET, an event is just a multicast delegate that represents all the registered event handlers. 引发事件即表明调用委派。Raising the event just means invoking the delegate. Visual Basic 语法通常隐藏与委派的交互,而编译器会在调用委派前复制它,如有关线程安全的注释中所述。Visual Basic syntax generally hides the interactions with the delegate, and the compiler copies the delegate before invoking it, as described in the note about thread safety. 在 Windows 运行时组件中创建自定义事件时,必须直接处理委托。When you create a custom event in a Windows Runtime component, you have to deal with the delegate directly. 这也意味着你可以使用 MulticastDelegate.GetInvocationList 方法(例如)为每个事件处理程序获取包含独立委托的数组,条件是你想要单独调用处理程序。This also means that you can, for example, use the MulticastDelegate.GetInvocationList method to get an array that contains a separate delegate for each event handler, if you want to invoke the handlers separately.