Ответ
Делегат — это тип, представляющий ссылку на метод. Событие — это конструкция языка, построенная на основе делегата, которая добавляет строгую инкапсуляцию для реализации шаблона «издатель-подписчик».
Делегат (Delegate)
- Это типобезопасный указатель на функцию.
- Экземпляр делегата можно создавать, передавать как параметр, вызывать.
- Нет встроенных ограничений: любой код с доступом к делегату может его вызвать, перезаписать или очистить список вызовов.
Событие (Event)
- Это обёртка над делегатом, которая ограничивает операции с ним извне класса-издателя.
- Реализует принцип инкапсуляции: издатель контролирует момент вызова, подписчики контролируют только подписку/отписку.
Ключевые различия на практике:
public class EventPublisher
{
// 1. Делегат как публичное поле (плохая практика для шаблона издатель-подписчик)
public Action? PublicDelegate;
// 2. Событие, построенное на основе делегата Action
public event Action? OnSomethingHappened;
public void DoWork()
{
// Вызов возможен и там, и там ВНУТРИ класса-издателя
PublicDelegate?.Invoke();
OnSomethingHappened?.Invoke();
}
}
public class Subscriber
{
public void Subscribe(EventPublisher publisher)
{
// РАБОТАЕТ: Подписка на событие (только +=, -=)
publisher.OnSomethingHappened += HandleEvent;
// РАБОТАЕТ: Подписка на делегат
publisher.PublicDelegate += HandleEvent;
// ОШИБКА КОМПИЛЯЦИИ: Нельзя вызвать событие извне класса-издателя.
// publisher.OnSomethingHappened?.Invoke();
// РАБОТАЕТ (и это проблема!): Можно вызвать делегат извне, нарушив инкапсуляцию.
publisher.PublicDelegate?.Invoke();
// ОШИБКА КОМПИЛЯЦИИ: Нельзя перезаписать всех подписчиков события.
// publisher.OnSomethingHappened = null;
// РАБОТАЕТ (и это большая проблема!): Можно удалить ВСЕХ подписчиков делегата.
publisher.PublicDelegate = null;
}
private void HandleEvent() => Console.WriteLine("Обработка.");
}
| Сводная таблица: | Операция | Делегат (поле) | Событие (event) |
|---|---|---|---|
| Вызов извне класса-издателя | Разрешен (del.Invoke()) |
Запрещен (только внутри класса) | |
Подписка (+=) |
Разрешена | Разрешена | |
Отписка (-=) |
Разрешена | Разрешена | |
Полная перезапись (=) |
Разрешена (del = null) |
Запрещена (только в классе-издателе) | |
| Цель | Общий механизм обратных вызовов | Безопасная реализация шаблона «издатель-подписчик» |
Вывод: Используйте event, когда нужно уведомлять внешние объекты о изменениях в вашем классе. Это защищает инварианты класса и предотвращает случайное или злонамеренное вмешательство в логику уведомлений.