Пользовательские события и методы доступа к событиям в компонентах среды выполнения Windows

Поддержка компонентов среда выполнения Windows в .NET упрощает объявление компонентов событий, скрывая различия между шаблоном событий универсальная платформа Windows (UWP) и шаблоном событий .NET. Однако при объявлении пользовательских методов доступа к событиям в компоненте среда выполнения Windows необходимо следовать шаблону, используемому в UWP.

Регистрация событий

При регистрации обработки события в UWP метод доступа add возвращает маркер. Для отмены регистрации передайте этот маркер в метод доступа remove. Это означает, что подписи методов доступа add и remove для событий UWP отличаются от подписей привычных методов доступа.

К счастью, компиляторы Visual Basic и C# упрощают этот процесс. При объявлении события с помощью пользовательских методов доступа в компоненте среда выполнения Windows компиляторы автоматически используют шаблон UWP. Например, если метод доступа add не возвращает маркер, возникает ошибка компилятора. .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

Статический метод GetOrCreateEventRegistrationTokenTable (Общий в Visual Basic) создает экземпляр объекта T EventRegistrationTokenTable<события> . Передайте в этот метод поле уровня класса, в котором будет храниться экземпляр таблицы токенов. Если поле оставлено пустым, метод создает таблицу, сохраняет ссылку на нее в поле и возвращает ссылку на эту таблицу. Если поле уже содержит ссылку на таблицу маркеров, то метод просто возвращает эту ссылку.

Важно Чтобы обеспечить потокобезопасность, поле, в котором хранится экземпляр события EventRegistrationTokenTable<T> , должно быть полем уровня класса. Если это поле является полем уровня класса, метод GetOrCreateEventRegistrationTokenTable гарантирует, что при попытке создания таблицы токенов несколькими потоками все потоки получают один и тот же экземпляр таблицы. Для заданного события все вызовы метода GetOrCreateEventRegistrationTokenTable должны использовать одно и то же поле уровня класса.

Вызов метода GetOrCreateEventRegistrationTokenTable в методе доступа remove и в методе RaiseEvent (метод OnRaiseEvent в C#) обеспечивает отсутствие исключений при вызове этих методов до добавления делегатов каких-либо обработчиков событий.

Другие члены класса EventRegistrationTokenTable<T>, используемые в шаблоне событий UWP, включают следующее:

  • Метод AddEventHandler создает маркер для делегата обработчика событий, сохраняет делегат в таблице, добавляет его в список вызовов и возвращает маркер.

  • При перегрузке метода RemoveEventHandler (EventRegistrationToken) делегат удаляется из таблицы и из списка вызовов.

    Примечание Методы AddEventHandler и RemoveEventHandler(EventRegistrationToken) блокируют таблицу, чтобы обеспечить потокобезопасность.

  • Свойство InvocationList возвращает делегат, включающий все обработчики событий, которые в настоящее время зарегистрированы для обработки события. Используйте этот делегат для порождения события или используйте методы класса Delegate для вызова обработчиков по отдельности.

    Примечание Рекомендуется следовать шаблону, приведенному в примере, приведенном ранее в этой статье, и скопировать делегат во временную переменную перед ее вызовом. Это позволяет избежать состояния гонки, при котором один поток удаляет последний обработчик, превращая делегат в null непосредственно перед тем, как другой поток попытается вызвать его. Делегаты неизменяемы, поэтому копия остается действительной.

Разместите свой код в методах доступа соответствующим образом. Если потокобезопасность важна, вы должны обеспечить собственную блокировку кода.

Для пользователей C#: при написании пользовательских методов доступа к событиям в соответствии с шаблоном событий UWP компилятор не поддерживает обычные синтаксические сокращения. При использовании в коде имени события компилятор возвращает ошибку.

Пользователи Visual Basic. В .NET событие — это просто делегат многоадресной рассылки, представляющий все зарегистрированные обработчики событий. Порождение события означает лишь вызов делегата. Синтаксис Visual Basic обычно скрывает взаимодействия с делегатом, а компилятор копирует делегат перед его вызовом, как сказано в примечании о потокобезопасности. При создании пользовательского события в компоненте среда выполнения Windows необходимо напрямую обращаться к делегату. Это также означает, что вы можете, например, с помощью метода MulticastDelegate.GetInvocationList получать массив, содержащий отдельный делегат для каждого обработчика событий, если требуется вызывать обработчики отдельно.