JavaRush /Курсы /C# SELF /Подписка на событие и создание обработчиков

Подписка на событие и создание обработчиков

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

1. Синтаксис подписки на событие

Представьте приложение для управления задачами: когда задача завершена, срабатывает событие, и обработчик может, например, отправить уведомление, обновить UI или записать информацию в лог. C# делает подписку на события удобной и безопасной: вы явно обозначаете, что конкретный обработчик реагирует на конкретное событие.

Подписка на событие в C# — это почти как добавить себя в список гостей на вечеринку:

publisher.MyEvent += HandlerMethod;

Здесь publisher ― объект, который объявил событие MyEvent, а HandlerMethod ― метод, который будет вызван при возникновении этого события.

Давайте посмотрим на это в контексте минимального примера. Допустим, у нас есть приложение для учёта количества нажатий:

public class Clicker
{
    public event Action Clicked;

    public void Click()
    {
        // Что-то было кликнуто!
        Clicked?.Invoke();
    }
}

Вот наш "издатель" события Clicked. Теперь — подпишем обработчик:

Clicker clicker = new Clicker();

void OnClicked()
{
    Console.WriteLine("Кнопка была нажата!");
}

clicker.Clicked += OnClicked;

// Где-то в коде
clicker.Click();
// → "Кнопка была нажата!"

Как это работает? На событие "нажатие" мы добавили наш метод OnClicked, и он будет вызываться каждый раз, когда происходит клик.

2. Обработчики событий: какие бывают и как их объявлять

Обработчик события — это метод, который будет вызван, когда сработает событие. Его сигнатура должна совпадать с типом делегата события. Например, если у нас событие объявлено как public event Action Clicked;, то обработчик должен быть методом без параметров и возвращаемого значения.

Обработчик для Action

void OnClicked() 
{
    Console.WriteLine("Событие произошло (Action)!");
}

Обработчик для стандартного EventHandler

Когда вы используете классический подход с EventHandler, обработчик принимает два параметра: отправителя (object sender) и данные события (EventArgs e):

public class Alarm
{
    public event EventHandler AlarmRaised;

    public void RaiseAlarm()
    {
        AlarmRaised?.Invoke(this, EventArgs.Empty);
    }
}

Alarm alarm = new Alarm();

void AlarmHandler(object sender, EventArgs e)
{
    Console.WriteLine("Сработала тревога!");
}

alarm.AlarmRaised += AlarmHandler;
alarm.RaiseAlarm();

Анонимные методы и лямбда-выражения

C# позволяет использовать анонимные функции и лямбда-выражения напрямую при подписке:

clicker.Clicked += () => Console.WriteLine("Another click!");

Или чуть сложнее, если событие с аргументами:

alarm.AlarmRaised += (sender, e) =>
{
    Console.WriteLine($"Alarm raised by: {sender}");
};

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

Подписка и отписка: важные нюансы

Настоящая жизнь — это когда гостей на вечеринке становится слишком много, или кто-то хочет уйти домой. С делегатами всё то же самое: обработчик можно добавить (подписаться) и убрать (отписаться):

// Подписка
publisher.MyEvent += MyHandler;

// Отписка (когда обработчик больше не нужен)
publisher.MyEvent -= MyHandler;

Почему это важно? Если не отписываться, особенно в больших приложениях, обработчики могут остаться "висящими" и не дать сборщику мусора очистить объекты — отсюда утечки памяти.

Несколько обработчиков

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

clicker.Clicked += () => Console.WriteLine("Первый обработчик!");
clicker.Clicked += () => Console.WriteLine("Второй обработчик!");

clicker.Click();
// → Первый обработчик!
// → Второй обработчик!

Вы даже можете подписывать и отписывать обработчики прямо "на лету" — это гибко и удобно.

Почему это действительно важно: реальные сценарии

  • UI (Windows Forms, WPF, WinUI, MAUI): клик по кнопке, изменение текста в поле — всё это события.
  • FileSystemWatcher: уведомление о появлении новых файлов в папке.
  • Асинхронные операции: завершение загрузки файла, прогресс выполнения задач.
  • Система плагинов: отдельные модули подписываются на события основного приложения.

Событийная модель позволяет строить расширяемые архитектуры: вы можете добавлять новые модули и подписываться на уже существующие события без изменения базового кодa.

Примеры подписки

Сигнатура события Пример подписки Пример обработчика
event Action
ev += Handler;
void Handler() { ... }
event Action<int>
ev += (x) => { ... };
void Handler(int x) { ... }
event EventHandler
ev += Handler;
void Handler(object s, EventArgs e) { ... }
event EventHandler<CustomArgs>
ev += Handler;
void Handler(object s, CustomArgs e) { ... }

5. Типичные ошибки и подводные камни

Ошибка №1: несовпадение сигнатур обработчика.
Если событие объявлено как event Action<int>, а вы пытаетесь подписать метод без параметров, компилятор выдаст ошибку. Всегда проверяйте, что метод соответствует требуемой сигнатуре события.

Ошибка №2: захват переменных в лямбдах.
При подписке через лямбда-выражение оно может захватывать переменные из окружающей области видимости (см. тему «Замыкания»). Если после подписки переменная изменится, обработчик будет видеть уже новое значение, что может привести к неожиданным результатам.

Ошибка №3: подписка на неинициализированное событие.
Если вы подписываетесь на событие до того, как создан и инициализирован объект, содержащий это событие, вы рискуете получить NullReferenceException. Перед подпиской убедитесь, что объект готов к использованию.

Ошибка №4: множественная подписка одного обработчика.
Если один и тот же обработчик подписан на событие несколько раз, он будет вызван столько же раз. Это не баг, а особенность работы событий, но часто оказывается неприятным сюрпризом.

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