Menu
Coddy logo textTech

Делегаты и события

Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 43 из 70.

Делегат — это тип, который представляет собой ссылку на метод. Представьте его как переменную, которая хранит метод вместо данных. Это позволяет передавать методы в качестве аргументов, сохранять их в коллекциях и вызывать их динамически.

Вы объявляете делегат, указывая сигнатуру метода, на которую он может ссылаться:

public delegate void Notify(string message);

public class Publisher
{
    public void SendAlert(string msg) => Console.WriteLine("Alert: " + msg);
}

// Использование
Notify notifier = new Publisher().SendAlert;
notifier("System started");  // Alert: System started

События базируются на делегатах для реализации паттерна «издатель-подписчик». Класс инициирует событие, а другие классы могут подписаться на получение уведомлений. Ключевое слово event ограничивает способы использования делегата — подписчики могут только добавлять или удалять обработчики, но не могут вызывать событие напрямую.

public class Button
{
    public event Action Clicked;
    
    public void Click()
    {
        Clicked?.Invoke();  // Уведомить всех подписчиков
    }
}

// Подписка
Button btn = new Button();
btn.Clicked += () => Console.WriteLine("Button was clicked!");
btn.Click();  // Кнопка была нажата!

Оператор += подписывает обработчик, в то время как -= отменяет подписку. Паттерн ?.Invoke() безопасно вызывает событие только в том случае, если есть подписчики. События имеют фундаментальное значение для GUI-приложений, игровых движков и любых систем, где объектам необходимо реагировать на изменения без сильной связанности.

challenge icon

Задание

Легко

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

Вы организуете свой код в двух файлах:

  • TemperatureMonitor.cs: Создайте класс TemperatureMonitor в пространстве имен Monitoring, который будет выступать в роли издателя. Ваш монитор должен:
    • Иметь закрытое поле (private field) для хранения текущей температуры
    • Объявить событие, используя Action<int>, с названием TemperatureChanged, которое срабатывает при каждом обновлении температуры
    • Иметь свойство Temperature (int), где сеттер обновляет закрытое поле и инициирует событие с новым значением температуры
    Не забудьте использовать паттерн ?.Invoke() для безопасного вызова события только тогда, когда есть подписчики.
  • Program.cs: В основном файле создайте экземпляр TemperatureMonitor и подпишите два разных обработчика на его событие TemperatureChanged:
    • Один обработчик, который выводит: Alert: Temperature is now {value} degrees
    • Другой обработчик, который выводит: Logged: {value}
    После подписки обоих обработчиков установите температуру дважды, используя значения из входных данных. Каждый раз при установке температуры должны срабатывать оба обработчика.

Вы получите два входных значения:

  • Первое значение температуры (целое число)
  • Второе значение температуры (целое число)

Например, если на вход подаются 25 и 30, вывод должен быть следующим:

Alert: Temperature is now 25 degrees
Logged: 25
Alert: Temperature is now 30 degrees
Logged: 30

Обратите внимание, как установка свойства Temperature автоматически уведомляет всех подписчиков — монитору не нужно ничего знать о том, кто его слушает. В этом и заключается прелесть событийно-ориентированного подхода!

Шпаргалка

Делегат — это тип, который представляет собой ссылку на метод, позволяя передавать методы в качестве аргументов, сохранять их в коллекциях и вызывать их динамически.

Объявите делегат, указав сигнатуру метода:

public delegate void Notify(string message);

public class Publisher
{
    public void SendAlert(string msg) => Console.WriteLine("Alert: " + msg);
}

// Использование
Notify notifier = new Publisher().SendAlert;
notifier("System started");  // Alert: System started

События реализуют паттерн «издатель-подписчик» с помощью делегатов. Ключевое слово event ограничивает использование делегата — подписчики могут только добавлять или удалять обработчики, но не могут вызывать событие напрямую.

public class Button
{
    public event Action Clicked;
    
    public void Click()
    {
        Clicked?.Invoke();  // Уведомить всех подписчиков
    }
}

// Подписка
Button btn = new Button();
btn.Clicked += () => Console.WriteLine("Button was clicked!");
btn.Click();  // Button was clicked!

Используйте += для подписки обработчика и -= для отмены подписки. Паттерн ?.Invoke() безопасно вызывает событие только в том случае, если есть подписчики.

Попробуйте сами

using System;
using Monitoring;

class Program
{
    public static void Main(string[] args)
    {
        // Чтение входных данных
        int temp1 = Convert.ToInt32(Console.ReadLine());
        int temp2 = Convert.ToInt32(Console.ReadLine());

        // TODO: Создайте экземпляр TemperatureMonitor

        // TODO: Подпишите обработчик, который выводит: "Alert: Temperature is now {value} degrees"

        // TODO: Подпишите еще один обработчик, который выводит: "Logged: {value}"

        // TODO: Установите температуру равной temp1

        // TODO: Установите температуру равной temp2
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование