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

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

События

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

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

Как правило, для вызова события добавляется метод, помеченный как protected и virtual (в C#) или Protected и Overridable (в Visual Basic). Назовите этот метод OnEventName; например, OnDataReceived. Метод должен принимать один параметр, который определяет объект данных события, являющийся объектом типа EventArgs или производного типа. Этот метод предоставляется, чтобы производные классы могли переопределять логику для вызова события. Производный класс должен вызывать метод OnEventName базового класса, чтобы зарегистрированные делегаты получили событие.

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

class Counter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        EventHandler handler = ThresholdReached;
        handler?.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> для событий, содержащих данные о событии. У этих делегатов нет типа возвращаемого значения. Они принимают два параметра (объект для источника события и объект для данных события).

Эти делегаты являются многоадресными, то есть в них могут храниться ссылки на несколько методов обработки событий. Дополнительные сведения см. на справочной странице класса 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 в качестве второго параметра в делегате. Если данные не предоставляются, можно передать значение 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 Program
{
    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 предоставляет свойства события, которые можно использовать вместе с другой структурой данных для хранения делегатов события.

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

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

См. также