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


Обработка и инициирование событий

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

Инициировать события с отправителем событий

Событие — это сообщение, отправленное объектом для сигнала о возникновении действия. Это может быть взаимодействие с пользователем, например нажатие кнопки или результатом другой логики программы, например изменение значения свойства. Объект, который вызывает событие, называется отправителем событий. Отправитель событий не знает объект или метод, который получает (обрабатывает) возникающие события. Событие обычно является членом отправителя события. Например, Click событие является членом Button класса, и PropertyChanged событие является членом класса, реализующего INotifyPropertyChanged интерфейс.

Чтобы определить событие, используйте событие C# или ключевое слово события Visual Basic в сигнатуре класса событий и укажите тип делегата для события. Делегаты описаны в следующем разделе.

Как правило, чтобы вызвать событие, добавьте метод, помеченный как protected и virtual (в C#) или ProtectedOverridable (в Visual Basic). Соглашение об именовании для метода, On<EventName>например OnDataReceived. Метод должен принимать один параметр, указывающий объект данных события, который является объектом типа EventArgs или производным типом. Вы предоставляете этот метод, чтобы производные классы могли переопределять логику вызова события. Производный класс всегда должен вызывать On<EventName> метод базового класса, чтобы гарантировать, что зарегистрированные делегаты получают событие.

В следующем примере показано, как объявить событие с именем ThresholdReached. Событие связано с EventHandler делегатом и вызывается в методе с именем OnThresholdReached:

class Counter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        ThresholdReached?.Invoke(this, e);
    }

    // provide remaining implementation for the class
}
Public Class Counter
    Public Event ThresholdReached As EventHandler

    Protected Overridable Sub OnThresholdReached(e As EventArgs)
        RaiseEvent ThresholdReached(Me, e)
    End Sub

    ' provide remaining implementation for the class
End Class

Декларация подписей делегатов для обработчиков событий

Делегат — это тип, содержащий ссылку на метод. Делегат объявляется с сигнатурой, которая определяет тип возвращаемого значения и параметры методов, на которые он ссылается. Он может содержать ссылки только на методы, соответствующие его сигнатуре. Делегат эквивалентен безопасному по типу указателю на функцию или обратному вызову. Объявление делегата достаточно для определения класса делегата.

Делегаты имеют множество применений в .NET. В контексте событий делегат — это промежуточный (или похожий на указатель механизм) между источником события и кодом, обрабатывающим событие. Вы связываете делегат с событием, включив тип делегата в объявление события, как показано в примере в предыдущем разделе. Дополнительные сведения о делегатах см. в Delegate классе.

.NET предоставляет EventHandler и EventHandler<TEventArgs> делегаты для поддержки большинства сценариев событий. Используйте делегат EventHandler для всех событий, которые не включают данных событий. Используйте делегат EventHandler<TEventArgs> для событий, включающих данные о событии. Эти делегаты не имеют возвращаемого значения и принимают два параметра: объект для источника события и объект для данных события.

Делегаты — это объекты класса multicast, что означает, что они могут содержать ссылки на несколько методов обработки событий. Дополнительные сведения см. на Delegate странице справки. Делегаты обеспечивают гибкость и точное управление событиями. Делегат действует как диспетчер событий для класса, который инициирует событие, поддерживая список зарегистрированных обработчиков событий для данного события.

Используйте делегаты EventHandler и EventHandler<TEventArgs> для определения необходимого делегата. В объявлении вы указываете делегат типа delegate в C# или типа Delegate в Visual Basic. В следующем примере показано, как объявить делегат с именем ThresholdReachedEventHandler:

public delegate void ThresholdReachedEventHandler(object sender, ThresholdReachedEventArgs e);
Public Delegate Sub ThresholdReachedEventHandler(sender As Object, e As ThresholdReachedEventArgs)

Работа с классами данных событий

Данные, связанные с событием, можно предоставить через класс данных событий. .NET предоставляет множество классов данных событий, которые можно использовать в приложениях. Например, SerialDataReceivedEventArgs класс — это класс данных события для SerialPort.DataReceived события. .NET следует шаблону именования, где все классы данных событий заканчиваются суффиксом EventArgs . Вы определяете, какой класс данных событий связан с событием, изучая делегат события. Например, SerialDataReceivedEventHandler делегат использует SerialDataReceivedEventArgs класс в качестве параметра.

Класс EventArgs обычно является базовым типом для классов данных событий. Этот класс также используется, если в событии нет данных, связанных с ним. При создании события, которое уведомляет подписчиков о том, что что-то произошло без дополнительных данных, включите класс EventArgs в делегат в качестве второго параметра. Вы можете передать значение EventArgs.Empty, если данные не предоставлены. Делегат EventHandler включает EventArgs класс в качестве параметра.

Можно создать класс, производный от EventArgs класса, чтобы предоставить все элементы, необходимые для передачи данных, связанных с событием. Как правило, следует использовать тот же шаблон именования, что и .NET, и заканчивать имя класса данных событий с помощью EventArgs суффикса.

В следующем примере показан класс данных событий с именем ThresholdReachedEventArgs , содержащий свойства, относящиеся к создаваемому событию:

public class ThresholdReachedEventArgs : EventArgs
{
    public int Threshold { get; set; }
    public DateTime TimeReached { get; set; }
}
Public Class ThresholdReachedEventArgs
    Inherits EventArgs

    Public Property Threshold As Integer
    Public Property TimeReached As DateTime
End Class

Реагирование на события с помощью обработчиков

Чтобы ответить на событие, необходимо определить метод обработчика событий в приемнике событий. Этот метод должен соответствовать сигнатуре делегата для обрабатываемого события. В обработчике событий выполняются действия, необходимые при возникновении события, например сбор входных данных пользователя после нажатия кнопки. Чтобы получать уведомления при возникновении события, метод обработчика событий должен подписаться на событие.

В следующем примере показан метод обработчика событий с именем c_ThresholdReached , соответствующий сигнатуре делегата EventHandler . Метод подписывается на ThresholdReached событие:

class ProgramTwo
{
    static void Main()
    {
        var c = new Counter();
        c.ThresholdReached += c_ThresholdReached;

        // provide remaining implementation for the class
    }

    static void c_ThresholdReached(object sender, EventArgs e)
    {
        Console.WriteLine("The threshold was reached.");
    }
}
Module Module1

    Sub Main()
        Dim c As New Counter()
        AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

        ' provide remaining implementation for the class
    End Sub

    Sub c_ThresholdReached(sender As Object, e As EventArgs)
        Console.WriteLine("The threshold was reached.")
    End Sub
End Module

Использование статических и динамических обработчиков событий

.NET позволяет подписчикам регистрировать уведомления о событиях статически или динамически. Статические обработчики событий действуют на протяжении всей жизни класса, события которого они обрабатывают. Динамические обработчики событий явно активируются и деактивируются во время выполнения программы, как правило, в ответ на некоторую логику условной программы. Динамические обработчики можно использовать, если уведомления о событиях необходимы только в определенных условиях или когда условия выполнения определяют конкретный обработчик для вызова. В примере в предыдущем разделе показано, как динамически добавить обработчик событий. Дополнительные сведения см. в разделе "События " (в Visual Basic) и "События " (в C#).

Запуск нескольких событий

Если класс вызывает несколько событий, компилятор создает одно поле на экземпляр делегата события. Если количество событий большое, стоимость хранения одного поля для каждого делегата может быть неприемлемой. В этих сценариях .NET предоставляет свойства событий, которые можно использовать с другой структурой данных, выбранной для хранения делегатов событий.

Свойства события состоят из объявлений событий, сопровождаемых средствами доступа к событиям. Методы доступа к событиям — это методы, которые определяются для добавления или удаления экземпляров делегатов событий из структуры данных хранилища.

Примечание.

Свойства события медленнее полей событий, так как каждый делегат события должен быть извлечен перед его вызовом.

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

В следующих ресурсах описываются другие задачи и понятия, связанные с работой с событиями:

Проверка ссылки на спецификацию

Справочная документация по спецификациям доступна для API, поддерживающих обработку событий:

Имя API Тип API Ссылка
EventHandler Делегировать EventHandler
EventHandler<TEventArgs> Делегировать EventHandler<TEventArgs>
EventArgs Класс EventArgs
Делегировать Класс Delegate