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


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

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

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

К счастью, компиляторы 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 (Shared в Visual Basic) GetOrCreateEventRegistrationTokenTable создает экземпляр объекта EventRegistrationTokenTable<T> события "ленивым" образом. Передайте в этот метод поле уровня класса, в котором будет храниться экземпляр таблицы токенов. Если поле оставлено пустым, метод создает таблицу, сохраняет ссылку ссылку на таблице в поле и возвращает ссылку на эту таблицу. Если поле уже содержит ссылку на таблицу токенов, то метод просто возвращает эту ссылку.

Важно!

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

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

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

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

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

    Примечание

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

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

    Примечание

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

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

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

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

См. также

Задачи

Практическое руководство. Реализация пользовательских методов доступа к событиям (Руководство по программированию в C#)

Ссылки

События (Руководство по программированию в C#)

Другие ресурсы

События (Visual Basic)

.NET Framework Support for Windows Store Apps and Windows Runtime