Делегаты и события
Часть раздела Объектно-ориентированное программирование путешествия по 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-приложений, игровых движков и любых систем, где объектам необходимо реагировать на изменения без сильной связанности.
Задание
ЛегкоДавайте создадим систему мониторинга температуры, которая использует делегаты и события для уведомления подписчиков при каждом изменении температуры. Это классический пример паттерна «издатель-подписчик» (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
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов