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("Ще один клік!");

Або трохи складніше, якщо подія має аргументи:

alarm.AlarmRaised += (sender, e) =>
{
    Console.WriteLine($"Тривогу згенерував: {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: сповіщення про появу нових файлів у теці.
  • Асинхронні операції: завершення завантаження файлу, прогрес виконання завдань.
  • Система плагінів: окремі модулі підписуються на події основного застосунку.

Подійна модель дає змогу будувати розширювані архітектури: ви можете додавати нові модулі та підписуватися на вже наявні події без змін базового коду.

Приклади підписки

Сигнатура події Приклад підписки Приклад обробника
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: множинна підписка одного обробника.
Якщо один і той самий обробник підписано на подію кілька разів, його викличуть стільки ж разів. Це не помилка, а особливість механізму подій, але часто стає неприємним сюрпризом.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ