← Назад к вопросам

Как происходит подписка на событие?

2.0 Middle🔥 152 комментариев
#Основы C# и .NET

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

📌 Подписка на события в C#

В C# подписка на события — это механизм, позволяющий объекту (подписчику) реагировать на действия, происходящие в другом объекте (источнике события). Это реализация паттерна наблюдатель (Observer), которая обеспечивает слабую связность между компонентами системы.

🧩 Основные компоненты

  1. Источник события (event publisher) — класс, который определяет событие и генерирует его.
  2. Подписчик (event subscriber) — класс, который регистрирует обработчик для события.
  3. Обработчик события (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;
}

⚠️ Распространённые проблемы и лучшие практики

  1. Утечки памяти: Не забывайте отписываться от событий, особенно если подписчик живет дольше источника.

    public void Unsubscribe(Publisher publisher)
    {
        publisher.CustomEvent -= OnCustomEvent;
    }
    
  2. Исключения в обработчиках: Исключение в одном обработчике прерывает вызов остальных. Для предотвращения этого используйте try-catch в каждом обработчике.

  3. Использование EventHandler<TEventArgs>: Рекомендуется для событий с данными вместо создания кастомных делегатов.

  4. Порядок вызова обработчиков: Не гарантируется, если явно не контролируется вызов делегатов.

📊 Пример полной реализации

// Пользовательские данные события
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#, который обеспечивает гибкую, слабосвязанную архитектуру приложений. Правильное использование событий позволяет создавать расширяемые и поддерживаемые системы, где компоненты взаимодействуют без прямых зависимостей.

Как происходит подписка на событие? | PrepBro