Что такое событие (event) в C#?

Ответ

Событие (event) в C# — это языковая конструкция, реализующая шаблон проектирования «Наблюдатель» (Observer) или «Издатель-Подписчик». Она позволяет объекту (издателю) уведомлять другие объекты (подписчиков) о возникновении какого-либо действия, не имея прямых ссылок на них.

Базовые компоненты:

  1. Делегат (тип события): Определяет сигнатуру метода, который могут вызывать подписчики (обычно EventHandler или EventHandler<TEventArgs>).
  2. Ключевое слово event: Объявляет событие как член класса. Оно инкапсулирует список вызовов делегатов (список подписчиков).
  3. Метод-инициатор: Вызывает событие, когда происходит соответствующее действие.
  4. Обработчики событий: Методы в классах-подписчиках, которые выполняются при срабатывании события.

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

Ответ 18+ 🔞

А, ну вот, опять эти ваши события в C#! Слушай, как будто на собеседовании попал, блядь. Ну ладно, разжую, как для дебила, только не ори.

Событие (event) — это такая хитрая жопа, чтобы один объект мог орать на всю деревню «Эй, пацаны, я щас чихну!», а другие, кто подписался, могли сказать «Ага, бля, услышали, сейчас нос вытрем». По-умному это «Наблюдатель» называется, но кому эта хуйня нужна, кроме интервьюеров?

Из чего состоит эта кухня:

  1. Делегат: Как договорённость. Все, кто хочет услышать чих, должны уметь сморкаться одинаково. Обычно все используют стандартный сморкатель EventHandler.
  2. Слово event: Это типа объявление: «Так, бля, тут будет чихание!». Оно прячет за собой список всех, кто уши навострил.
  3. Метод, который орет «Апчхи!»: Вызывает событие, когда приспичило.
  4. Обработчики: Это и есть те самые методы сморкания у всех подписчиков.

Пример, чтобы совсем доходило, как кнопка в интерфейсе:

// 1. Это наш чихальщик (Издатель)
public class Button
{
    // 2. Объявляем, что у нас есть событие «Кликнули». Все будут использовать стандартный договор `EventHandler`.
    public event EventHandler Clicked;

    // 3. Метод, который орет «Апчхи!» (стандартный защищённый шаблон, запомни его, дурачок)
    protected virtual void OnClicked()
    {
        // Хитрость! Копируем ссылку в локальную переменную, чтобы если кто-то отпишется *прямо сейчас*, мы не вылетели с ошибкой.
        EventHandler handler = Clicked;
        // Если есть хоть один подписчик (не null), орем на всех.
        handler?.Invoke(this, EventArgs.Empty);
    }

    public void SimulateClick()
    {
        Console.WriteLine("Кнопку нажали, ёпта.");
        OnClicked(); // Кричим всем, кто слушает!
    }
}

// 4. А это подписчик, который любит всё логировать
public class Logger
{
    public void LogButtonClick(object sender, EventArgs e)
    {
        Console.WriteLine($"Кнопка кликнута в {DateTime.Now}. Кричал: {sender}");
    }
}

// Как этим пользоваться:
var myButton = new Button();
var logger = new Logger();

// Подписываемся на чих. Логгер теперь будет сморкаться.
myButton.Clicked += logger.LogButtonClick;
// Можно и анонимно подписать, чтоб два раза не вставать.
myButton.Clicked += (s, e) => Console.WriteLine("Анонимный обработчик тоже сработал!");

// Симулируем клик — и понеслась хуйня по кочкам!
myButton.SimulateClick();
// На экране будет:
// Кнопку нажали, ёпта.
// Кнопка кликнута в ... Кричал: Button
// Анонимный обработчик тоже сработал!

// Чтобы не засорять память, отписываемся.
myButton.Clicked -= logger.LogButtonClick;

Важные фишки, чтобы не обосраться:

  • Инкапсуляция, блядь: Снаружи класса ты можешь только += и -=. Нельзя взять и всем подписчикам сказать = null или самому вызвать событие. Это хорошо.
  • Потокобезопасность — голова болит: Всегда копируй событие в локальную переменную перед вызовом, как в OnClicked(). Иначе в многопоточке можно поймать исключение, если подписчик отпишется в самый неподходящий момент.
  • Отписывайся, мудень: Если объект-подписчик живёт меньше, чем объект-издатель события, и не отпишется — это утечка памяти, он будет висеть мёртвым грузом. Сборщик мусора его не заберёт, потому что на него есть ссылка из списка подписчиков. Так что не забывай про -=.