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) 方法重载从表和调用列表中删除委托。

    备注

    AddEventHandlerRemoveEventHandler(EventRegistrationToken) 方法锁定表,以帮助确保线程安全。

  • InvocationList 属性返回一个委托,其中包括当前注册用于处理事件的所有事件处理程序。使用此委托引发事件,或使用 Delegate 类的方法逐个调用处理程序。

    备注

    建议你遵循本文前面提供的示例中显示的模式,并在调用委托之前将其复制到一个临时变量中。这样可以避免出现争用情况:一个线程移除最后一个处理程序,在其他线程尝试调用委托之前将委托减少至 null。委托是不可变的,因此副本仍然有效。

酌情将你自己的代码放在访问器中。如果线程安全是个问题,则必须为代码提供锁定。

C# 用户:在 Windows 运行时事件模式中编写自定义事件访问器时,编译器不提供常用的语法快捷方式。如果在代码中使用事件的名称,则会生成错误。

Visual Basic 用户:在 .NET Framework 中,事件只是表示所有已注册事件处理程序的多播委托。引发事件仅意味着调用该委托。Visual Basic 语法通常会隐藏与委托的交互,编译器在调用委托之前会复制委托,如有关线程安全的说明中所述。在 Windows 运行时组件中创建自定义事件时,必须直接处理委托。这也意味着,如果要分别调用处理程序,则可使用 MulticastDelegate.GetInvocationList 之类的方法为每个事件处理程序获取包含单独委托的数组。

请参见

任务

如何:实现自定义事件访问器(C# 编程指南)

参考

事件(C# 编程指南)

其他资源

事件 (Visual Basic)

.NET Framework 对 Windows 应用商店应用程序和 Windows 运行时的支持情况