Поделиться через


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

Поддержка .NET для компонентов среда выполнения Windows упрощает объявление компонентов событий, скрывая различия между шаблоном событий универсальная платформа 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

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

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

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

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

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

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

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

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

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

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

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

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