JavaRush /Курсы /C# SELF /Шаблон «Издатель-Подписчик» и события в C# (

Шаблон «Издатель-Подписчик» и события в C# ( event)

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

1. Введение

Работая с программами, вы неизбежно сталкиваетесь с ситуациями, когда одна часть должна «известить» другие о том, что произошло нечто важное. Классический пример — пользователь щёлкнул мышью, и это событие требует обработки. В повседневной жизни мы уже существуем в мире событий: на кухне засвистел чайник — вы услышали сигнал и поспешили выключить плиту. Кофе пролился на клавиатуру — сердце екнуло — и вы бросились спасать ноутбук. Программирование следует тем же законам.

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

В C# события — специальная конструкция на основе типа делегата. Делегат задаёт сигнатуру обратного вызова — то, что и как будет вызвано у подписчиков. Объявление события выполняется ключевым словом event, а делегат — ключевым словом delegate.

Зачем нужны события?

  • Ослабленная связь: Издатель ничего не знает о подписчиках — только подаёт сигнал.
  • Гибкость архитектуры: Можно динамически добавлять и убирать обработчики, не меняя код издателя.
  • Масштабируемость: Добавили нового подписчика — и он тут же начал получать уведомления.

Классический шаблон «Издатель-Подписчик»

Представим, что у нас есть класс «Пожарная сигнализация» (издатель) и класс «Человек в здании» (подписчик). Когда срабатывает сигнализация, она подаёт сигнал всем одновременно — неважно, сколько людей находится в здании и где именно. Это и есть шаблон «Издатель-Подписчик» (или Observer).

Издатель не знает, сколько и каких подписчиков имеется — он просто уведомляет, а остальные сами подписываются на уведомления или игнорируют их.

Как это работает в C#?

  • Издатель: определяет событие (event), предоставляет подписку/отписку.
  • Подписчик: подписывается на событие и реализует обработчик (метод-обработчик будет вызван при наступлении события).

2. События на практике: первый пример

Переходя от дома к коду, опишем простейшую модель. Допустим, у нас консольное приложение, где объект-таймер каждую секунду «тикает», а разные обработчики реагируют (например, выводят «тик» в консоль или считают количество тиков).

Шаг 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 напрямую писал в консоль, класс оказался бы жёстко связан с конкретным действием. События же «освобождают» код: таймер не знает, что именно будут делать подписчики — запускать ракету, логировать в файл или отправлять e-mail.

Важное отличие событий и делегатов

  • Делегат — «указатель» на метод, а событие — это делегат с ограничениями доступа.
  • Подписчики могут только подписываться/отписываться; вызвать событие извне нельзя — это может сделать только сам издатель.
  • Чтобы объявить событие, добавьте модификатор 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.

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