1. Вступ
Працюючи з програмами, ви неминуче стикаєтеся із ситуаціями, коли одна частина має «сповістити» інші про те, що відбулося щось важливе. Класичний приклад: користувач клікнув мишею — цю подію слід обробити. У повсякденному житті ми живемо у світі подій: на кухні засвистів чайник — ви почули сигнал і поспішили вимкнути плиту. Кава пролилася на клавіатуру — серце завмерло — і ви кинулися рятувати ноутбук. Програмування підкоряється тим самим законам.
Подія — це механізм, що дозволяє обʼєкту-джерелу (видавцю) сповіщати інші обʼєкти (підписників) про зміни або дії, які відбулися. Це щось на кшталт: «я оголосив — хто почув, той відгукнувся».
У C# події — спеціальна конструкція на основі делегата. Делегат визначає сигнатуру зворотного виклику — що саме і як буде викликано в підписників. Подію оголошують ключовим словом event, а делегата — ключовим словом delegate.
Навіщо потрібні події?
- Слабке звʼязування: видавець нічого не знає про підписників — лише подає сигнал.
- Гнучкість архітектури: можна динамічно додавати й прибирати обробники, не змінюючи код видавця.
- Масштабованість: додали нового підписника — і він одразу почав отримувати сповіщення.
Класичний шаблон «Видавець-Підписник»
Уявімо, що є клас «Пожежна сигналізація» (видавець) і клас «Людина в будівлі» (підписник). Коли спрацьовує сигналізація, вона подає сигнал усім одночасно — байдуже, скільки людей у будівлі й де саме. Це і є шаблон «Видавець-Підписник» (або Observer).
Видавець не знає, скільки й яких підписників існує — він просто сповіщає, а решта самі підписуються на сповіщення або ігнорують їх.
Як це працює в C#?
- Видавець: визначає подію (event) й дає змогу підписуватися та відписуватися.
- Підписник: підписується на подію та реалізує обробник (метод-обробник буде викликаний під час настання події).
2. Події на практиці: перший приклад
Від побуту до коду: опишемо найпростішу модель. Припустимо, у нас консольний застосунок, де обʼєкт-таймер щосекунди «тикає», а різні обробники реагують (наприклад, виводять «тік» у консоль або рахують кількість тiків).
Крок 1. Визначаємо делегат і подію
public class SimpleTimer
{
// Оголошуємо делегата для події
public delegate void TickEventHandler(object sender, EventArgs e);
// Подія на основі делегата
public event TickEventHandler Tick;
public void Start(int count)
{
for (int i = 0; i < count; i++)
{
System.Threading.Thread.Sleep(1000); // імітація тіку
OnTick(); // згенерувати подію
}
}
protected virtual void OnTick()
{
// Викликаємо подію, якщо є підписники (Tick != null)
Tick?.Invoke(this, EventArgs.Empty);
}
}
Що тут відбувається?
- Визначено делегат TickEventHandler із класичною сигнатурою object sender, EventArgs e.
- Подія Tick — точка підписки для обробників.
- Метод Start імітує «тикання» та періодично викликає OnTick.
- У OnTick подію викликаємо безпечно: Tick?.Invoke(..., EventArgs.Empty).
Крок 2. Підписка на подію
class Program
{
static void Main()
{
var timer = new SimpleTimer();
// Підписуємося на подію Tick
timer.Tick += Timer_Tick;
timer.Start(3);
// Можна відписатися, якщо треба
timer.Tick -= Timer_Tick;
}
static void Timer_Tick(object sender, EventArgs e)
{
Console.WriteLine("Тік!");
}
}
Створюємо таймер, підписуємося оператором +=, далі під час кожного тіку викликається обробник. Відписка — оператор -=.
3. Корисні нюанси
Чому події кращі за «жорсткі» виклики?
Якби SimpleTimer у OnTick безпосередньо писав у консоль, клас був би жорстко привʼязаний до конкретної дії. А події «розвʼязують» код: таймер не знає, що саме робитимуть підписники — запускати ракету, логувати у файл чи надсилати електронні листи.
Важлива різниця між подіями та делегатами
- Делегат — «вказівник» на метод, а подія — це делегат з обмеженнями доступу.
- Підписники можуть лише підписуватися/відписуватися; викликати подію ззовні не можна — це може зробити лише сам видавець.
- Щоб оголосити подію, додайте модифікатор event до типу делегата — компілятор забезпечить коректну модель доступу.
Коротка схема роботи події в C#
+------------------+ +------------------------------+
| | | |
| Видавець | <------> | Підписник |
| (Publisher/Event)| | (Subscriber/Handler) |
| | | |
+------------------+ +------------------------------+
| 1) оголошує подію | 2) підписується на неї
| 3) викликає її | 4) реалізує обробник
Коли використовувати події?
- Потрібно сповістити невизначену кількість слухачів про те, що сталося.
- Не хочете жорстко звʼязувати логіку дій усередині класу-джерела.
- UI, асинхронна взаємодія, системні сповіщення — усе крутиться навколо подій.
Короткі особливості реалізації подій у C#
- Подію не можна викликати «ззовні»: тільки код видавця має право на Invoke.
- Підписка/відписка: оператори +=/-=; обробників може бути кілька.
- Подія — це фактично список делегатів: під час настання події викликаються всі обробники в порядку підписки.
- Рекомендовані делегати: використовуйте EventHandler і EventHandler<TEventArgs> для сумісності з .NET-екосистемою.
4. Типові помилки новачків
Забувають перевіряти, що є підписники: Tick != null. Краще використовувати безпечний виклик: Tick?.Invoke(...).
Підписуються на подію, але не відписуються, коли обробник уже не потрібен. Це може утримувати обʼєкти в памʼяті та призводити до витоків памʼяті.
Намагаються «викликати» подію ззовні — компілятор не дозволить. Не можна написати щось на кшталт game.GameOver(), якщо це подія, а не метод.
Не дотримуються сигнатури делегата. Для подій використовуйте стандартні EventHandler або EventHandler<TEventArgs> — так код сумісний із рештою бібліотек .NET.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ