Как правильно подписаться на событие и отписаться от него в C#?

«Как правильно подписаться на событие и отписаться от него в C#?» — вопрос из категории C# Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Подписка на событие — это добавление метода-обработчика в список вызовов события. Делается это с помощью оператора +=. Отписка (критически важная для избежания утечек памяти) — с помощью оператора -=.

Базовый пример

public class Button
{
    public event EventHandler Clicked; // Объявление события

    public void SimulateClick()
    {
        Clicked?.Invoke(this, EventArgs.Empty); // Вызов события
    }
}

// Где-то в коде:
Button myButton = new Button();

// 1. Подписка с помощью именованного метода
myButton.Clicked += OnButtonClicked;

// 2. Отписка (например, при закрытии формы)
myButton.Clicked -= OnButtonClicked;

void OnButtonClicked(object sender, EventArgs e)
{
    Console.WriteLine("Кнопка была нажата.");
}

Использование лямбда-выражений и анонимных методов

Удобно для коротких обработчиков, но требует сохранения ссылки для отписки.

// Подписка с помощью лямбда-выражения
EventHandler handler = (s, e) => Console.WriteLine("Лямбда-обработчик!");
myButton.Clicked += handler;

// Для отписки нужна ссылка на ту же самую лямбду
myButton.Clicked -= handler;

Ключевые практики и подводные камни

  1. Всегда отписывайтесь. Если объект, содержащий метод-обработчик, живет дольше, чем объект-источник события, на который он подписан, сборщик мусора не сможет удалить источник. Это классическая утечка памяти. Отписывайтесь в методах Dispose, OnClosing и т.д.
  2. Потокобезопасность. Операции += и -= по умолчанию не являются атомарными. В многопоточных сценариях при одновременной подписке/отписке может возникнуть состояние гонки. Для потокобезопасной реализации событий используйте ключевое слово event с аксессорами add/remove и примитивами синхронизации (например, lock).
  3. Проверка на null. Всегда используйте null-условный оператор (?.) при вызове события, как показано в примере SimulateClick(). Это защищает от NullReferenceException, если на событие нет подписчиков.
  4. Использование слабых ссылок. В сложных сценариях (например, плагины) для избежания утечек памяти можно использовать шаблон слабой ссылки (WeakReference или WeakEventManager в WPF), но это требует дополнительной инфраструктуры.