Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
📌 Подписка на события в C#
В C# подписка на события — это механизм, позволяющий объекту (подписчику) реагировать на действия, происходящие в другом объекте (источнике события). Это реализация паттерна наблюдатель (Observer), которая обеспечивает слабую связность между компонентами системы.
🧩 Основные компоненты
- Источник события (event publisher) — класс, который определяет событие и генерирует его.
- Подписчик (event subscriber) — класс, который регистрирует обработчик для события.
- Обработчик события (event handler) — метод, выполняемый при возникновении события.
🔧 Техническая реализация
1. Объявление события
Событие объявляется с использованием ключевого слова event и делегата. Чаще всего используется встроенный делегат EventHandler или его универсальная версия EventHandler<TEventArgs>.
public class Publisher
{
// Стандартное событие без данных
public event EventHandler? StandardEvent;
// Событие с пользовательскими данными
public event EventHandler<CustomEventArgs>? CustomEvent;
// Событие на основе кастомного делегата
public delegate void CustomDelegate(string message);
public event CustomDelegate? CustomDelegateEvent;
}
2. Подписка на событие
Подписка осуществляется с помощью оператора +=. Обработчик должен соответствовать сигнатуре делегата события.
public class Subscriber
{
public void Subscribe(Publisher publisher)
{
// Подписка с использованием лямбда-выражения
publisher.StandardEvent += (sender, e) =>
{
Console.WriteLine("Событие произошло!");
};
// Подписка с использованием метода
publisher.CustomEvent += OnCustomEvent;
// Подписка на кастомное событие
publisher.CustomDelegateEvent += OnCustomDelegateEvent;
}
private void OnCustomEvent(object? sender, CustomEventArgs e)
{
Console.WriteLine($"Получены данные: {e.Data}");
}
private void OnCustomDelegateEvent(string message)
{
Console.WriteLine($"Сообщение: {message}");
}
}
3. Генерация события
Для вызова события используется специальный метод, обычно называемый OnEventName, который проверяет наличие подписчиков.
public class Publisher
{
public event EventHandler? StandardEvent;
public event EventHandler<CustomEventArgs>? CustomEvent;
protected virtual void OnStandardEvent()
{
// Проверка наличия подписчиков
StandardEvent?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnCustomEvent(CustomEventArgs e)
{
CustomEvent?.Invoke(this, e);
}
public void DoSomething()
{
// Выполнение действия и вызов события
OnStandardEvent();
OnCustomEvent(new CustomEventArgs { Data = "Пример данных" });
}
}
💡 Важные особенности
Синтаксис подписки
- Добавление обработчика:
publisher.EventName += handlerMethod; - Удаление обработчика:
publisher.EventName -= handlerMethod; - Проверка наличия подписчиков:
EventName?.Invoke(...)(null-условный оператор)
Потокобезопасность событий
Для thread-safe реализации рекомендуется использовать локальную копию делегата:
protected virtual void OnEvent()
{
EventHandler? handler = StandardEvent;
handler?.Invoke(this, EventArgs.Empty);
}
Использование анонимных методов и лямбда-выражений
publisher.StandardEvent += delegate(object sender, EventArgs e)
{
Console.WriteLine("Анонимный обработчик");
};
publisher.StandardEvent += (s, e) => Console.WriteLine("Лямбда-обработчик");
События в интерфейсах
События могут быть объявлены в интерфейсах, что обеспечивает контракт для реализации:
public interface IObservable
{
event EventHandler SomethingHappened;
}
⚠️ Распространённые проблемы и лучшие практики
-
Утечки памяти: Не забывайте отписываться от событий, особенно если подписчик живет дольше источника.
public void Unsubscribe(Publisher publisher) { publisher.CustomEvent -= OnCustomEvent; } -
Исключения в обработчиках: Исключение в одном обработчике прерывает вызов остальных. Для предотвращения этого используйте try-catch в каждом обработчике.
-
Использование
EventHandler<TEventArgs>: Рекомендуется для событий с данными вместо создания кастомных делегатов. -
Порядок вызова обработчиков: Не гарантируется, если явно не контролируется вызов делегатов.
📊 Пример полной реализации
// Пользовательские данные события
public class TemperatureChangedEventArgs : EventArgs
{
public double OldTemperature { get; }
public double NewTemperature { get; }
public TemperatureChangedEventArgs(double oldTemp, double newTemp)
{
OldTemperature = oldTemp;
NewTemperature = newTemp;
}
}
// Источник события
public class Thermostat
{
private double _temperature;
public event EventHandler<TemperatureChangedEventArgs>? TemperatureChanged;
public double Temperature
{
get => _temperature;
set
{
if (_temperature != value)
{
double oldTemp = _temperature;
_temperature = value;
OnTemperatureChanged(oldTemp, value);
}
}
}
protected virtual void OnTemperatureChanged(double oldTemp, double newTemp)
{
TemperatureChanged?.Invoke(this,
new TemperatureChangedEventArgs(oldTemp, newTemp));
}
}
// Подписчик
public class Display
{
public Display(Thermostat thermostat)
{
thermostat.TemperatureChanged += OnTemperatureChanged;
}
private void OnTemperatureChanged(object? sender, TemperatureChangedEventArgs e)
{
Console.WriteLine($"Температура изменилась с {e.OldTemperature}°C на {e.NewTemperature}°C");
}
}
Подписка на события — фундаментальный механизм C#, который обеспечивает гибкую, слабосвязанную архитектуру приложений. Правильное использование событий позволяет создавать расширяемые и поддерживаемые системы, где компоненты взаимодействуют без прямых зависимостей.