Ответ
Событие (event) в C# — это языковая конструкция, реализующая шаблон проектирования «Наблюдатель» (Observer) или «Издатель-Подписчик». Она позволяет объекту (издателю) уведомлять другие объекты (подписчиков) о возникновении какого-либо действия, не имея прямых ссылок на них.
Базовые компоненты:
- Делегат (тип события): Определяет сигнатуру метода, который могут вызывать подписчики (обычно
EventHandlerилиEventHandler<TEventArgs>). - Ключевое слово
event: Объявляет событие как член класса. Оно инкапсулирует список вызовов делегатов (список подписчиков). - Метод-инициатор: Вызывает событие, когда происходит соответствующее действие.
- Обработчики событий: Методы в классах-подписчиках, которые выполняются при срабатывании события.
Практический пример (UI-кнопка):
// 1. Издатель (Publisher)
public class Button
{
// 2. Объявление события. Используется стандартный делегат EventHandler.
public event EventHandler Clicked;
// 3. Метод, инициирующий событие (protected virtual — стандартный шаблон).
protected virtual void OnClicked()
{
// Сохраняем ссылку на делегат в локальную переменную для потокобезопасности.
EventHandler handler = Clicked;
// Если есть подписчики (handler не null), вызываем событие.
handler?.Invoke(this, EventArgs.Empty);
}
public void SimulateClick()
{
Console.WriteLine("Button was pressed.");
OnClicked(); // Запускаем уведомление подписчиков.
}
}
// 4. Подписчик (Subscriber)
public class Logger
{
public void LogButtonClick(object sender, EventArgs e)
{
Console.WriteLine($"Button clicked at {DateTime.Now}. Sender: {sender}");
}
}
// Использование:
var myButton = new Button();
var logger = new Logger();
// Подписка на событие (добавление обработчика в список вызовов).
myButton.Clicked += logger.LogButtonClick;
// Можно подписать несколько методов.
myButton.Clicked += (s, e) => Console.WriteLine("Anonymous handler fired!");
// Имитация клика — вызовутся ВСЕ подписанные обработчки.
myButton.SimulateClick();
// Вывод:
// Button was pressed.
// Button clicked at ... Sender: Button
// Anonymous handler fired!
// Отписка от события.
myButton.Clicked -= logger.LogButtonClick;
Ключевые особенности и best practices:
- Инкапсуляция: Вне класса-издателя событие можно только
+=(подписаться) и-=(отписаться), но нельзя=(перезаписать всех подписчиков) или вызватьInvoke(). - Потокобезопасность: Перед вызовом всегда копируйте событие в локальную переменную (как в
OnClicked()), чтобы избежать исключенияNullReferenceException, если подписчик отпишется между проверкой на null и вызовом. - Отписка: Чтобы избежать утечек памяти, объекты должны отписываться от событий, время жизни которых превышает время жизни самого объекта.