JavaRush /Курсы /C# SELF /Создание событий с помощью делегатов (

Создание событий с помощью делегатов ( event и delegate)

C# SELF
52 уровень , 1 лекция
Открыта

1. Введение

Итак, когда мы говорим о «событии» (event) в контексте C#, мы имеем в виду механизм, который позволяет безопасно уведомить один или несколько объектов о том, что что-то произошло. События — это не магия: под капотом это делегаты (ключевое слово delegate) с дополнительной защитой от неправильного использования.

Представьте подписку на YouTube-канал. У канала (объекта-владельца события) есть кнопка «Подписаться» — любой зритель (другой объект) может нажать её и попасть в список подписчиков (делегатов-обработчиков). Когда автор выкладывает новое видео (вызывает событие), уведомления получают только подписчики, и только автор канала решает, когда это произойдёт. Важная деталь: зрители могут подписаться или отписаться, но не могут сами отправить уведомление — это право есть только у владельца канала.

Немного формального синтаксиса


// Объявление делегата, который будет определять "форму" обработчика события
public delegate void SimpleEventHandler();

// Объявление события на основе делегата
public event SimpleEventHandler SomethingHappened;

Всё просто: event — это ключевое слово для объявления события, а тип события всегда задаётся делегатом.

2. Первый простой пример: событие «На кнопку нажали!»

Шаг 1. Определяем делегат для обработчиков события


// Обработчик события: ничего не возвращает, не принимает параметров
public delegate void ButtonClickHandler();

Шаг 2. Класс с событием


public class Button
{
    // Событие: можно подписываться и отписываться, но вызвать его извне нельзя
    public event ButtonClickHandler Click;

    // Метод, который "нажимает кнопку"
    public void Press()
    {
        Console.WriteLine("Кнопка нажата!");
        // Вызов события: оповестить всех подписчиков
        Click?.Invoke();
    }
}

Обратите внимание: событие объявлено на основе делегата, а в методе Press событие вызывается через ?.Invoke(). Почему так? Потому что событие может быть пустым (никто не подписался), тогда Click равен null. Оператор безопасного вызова гарантирует, что обработчики вызовутся только при наличии подписчиков.

Шаг 3. Подписка на событие и запуск кода


// Пример использования
public class Program
{
    public static void Main()
    {
        var button = new Button();

        // Добавляем "слушателя" (подписываемся на событие)
        button.Click += OnButtonClicked;
        button.Click += () => Console.WriteLine("Eще один обработчик!");

        button.Press(); // Симулируем нажатие кнопки

        // Можно отписаться от события
        button.Click -= OnButtonClicked;
        button.Press();
    }

    // Обычный обработчик события
    public static void OnButtonClicked()
    {
        Console.WriteLine("Обработчик: Кнопка была нажата!");
    }
}

При первом нажатии отработают оба обработчика, при втором — только лямбда.

3. Отличия события от делегата

К делегату можно свободно присваивать значения и даже полностью заменить список подписчиков — кто-то может написать что-то вроде button.Click = null и все предыдущие подписки «слетят».

С событием всё иначе — оно привязано к объекту строже. Только владелец класса, в котором событие объявлено, может вызвать его напрямую (например, Click() изнутри класса). Любой другой код может лишь подписываться через += или отписываться через -=, но не может «обнулить» всё разом. Это защищает инкапсуляцию и не позволяет «сломать» систему подписок.

4. Сигнатура: типы делегатов для событий, параметры

Традиция в .NET такова: обработчик событий получает два параметра — object sender (кто вызвал событие) и аргументы события (EventArgs). Рекомендуется использовать делегаты EventHandler или EventHandler<TEventArgs>.

Пример: делегат с параметрами


public delegate void ButtonClickHandler(object sender, EventArgs e);

EventArgs — базовый класс для передачи дополнительной информации. Если нужно больше данных, создаём свой наследник.

Применим это к нашей кнопке


// Класс аргументов события
public class ButtonClickEventArgs : EventArgs
{
    public string UserName { get; }

    public ButtonClickEventArgs(string userName)
    {
        UserName = userName;
    }
}

public class Button
{
    public event EventHandler<ButtonClickEventArgs> Click;

    public void Press(string userName)
    {
        Console.WriteLine("Кнопка нажата!");
        Click?.Invoke(this, new ButtonClickEventArgs(userName));
    }
}

public static void Main()
{
    var button = new Button();
    button.Click += OnButtonClicked;

    button.Press("Василий");
}

public static void OnButtonClicked(object sender, ButtonClickEventArgs e)
{
    Console.WriteLine($"Пользователь {e.UserName} нажал на кнопку!");
}

5. Визуальная схема: как работает событие


+-------------+
| Пользователь|
+-------------+
      |
      v
+--------------+
|  Button.Press|
+--------------+
      |
      v
+-----------------+         +------------------------+
|  Вызов Click?   |----->---|   Подписчик 1          |
+-----------------+         +------------------------+
      |                     |   Выполнить обработчик |
      v                     +------------------------+
+-----------------+         +------------------------+
|   Если подписчик|----->---|   Подписчик 2          |
+-----------------+         +------------------------+
      :                     |   Выполнить обработчик |
      v                     +------------------------+

Button.Press вызывает событие; обработчики подписчиков выполняются по очереди.

6. Полезные нюансы

Рекомендуемая форма событий в .NET

Используйте EventHandler и EventHandler<TEventArgs>, чтобы код был совместим с библиотеками и инструментами. Такой подход упрощает эволюцию событий: вы добавляете новые свойства в свои EventArgs без ломки подписчиков.

Документация: EventHandler, event.

События vs делегаты

Если нужно связать один компонент с одним конкретным методом — достаточно делегата (delegate). Если же требуется, чтобы разные части программы могли подписываться и отписываться в любое время — используйте событие (event).

7. Типовые ошибки и неловкие моменты при создании событий

Ошибка №1: вызов события без проверки на null. Если у события нет подписчиков, прямая попытка вызвать его приведёт к NullReferenceException. Используйте безопасный вызов:


Click?.Invoke(...);

Ошибка №2: попытка вызвать событие извне класса. Событие можно поднять (вызвать) только внутри того класса, где оно объявлено. Из другого класса компилятор выдаст ошибку — это защищает инкапсуляцию.

Ошибка №3: множественная подписка на один и тот же обработчик. Если один обработчик подписан несколько раз, он будет вызван столько же раз. Это особенность механизма подписки. Следите за дублированием += и корректной отпиской -=.

2
Задача
C# SELF, 52 уровень, 1 лекция
Недоступна
Реализация простого события "На кнопку нажали"
Реализация простого события "На кнопку нажали"
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ