Что такое поведение события (event) по умолчанию в C#?

Ответ

По умолчанию событие в C# — это обёртка над multicast-делегатом. При вызове события все подписанные обработчики вызываются последовательно в порядке их добавления через оператор +=. Если у события нет подписчиков, его вызов через ?.Invoke() безопасно возвращает null и ничего не происходит.

Ключевые особенности:

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

Пример стандартного использования:

public class Publisher
{
    public event EventHandler<EventArgs> OnDataProcessed;

    public void ProcessData()
    {
        // Логика обработки...
        OnDataProcessed?.Invoke(this, EventArgs.Empty); // Вызов всех подписчиков
    }
}

public class Subscriber
{
    public void Subscribe(Publisher pub)
    {
        pub.OnDataProcessed += HandleNotification;
    }

    private void HandleNotification(object sender, EventArgs e)
    {
        Console.WriteLine("Данные обработаны.");
    }
}

Кастомное поведение (add/remove аксессоры): Для реализации собственной логики подписки (например, логирования, гарантированного вызова всех обработчиков или weak references) можно переопределить аксессоры add и remove:

private EventHandler _customEvent;
public event EventHandler CustomEvent
{
    add
    {
        Console.WriteLine($"Добавлен обработчик: {value.Method.Name}");
        _customEvent += value;
    }
    remove
    {
        Console.WriteLine($"Удалён обработчик: {value.Method.Name}");
        _customEvent -= value;
    }
}

Ответ 18+ 🔞

А, ну вот, опять эти события в C#. Слушай, как будто в первый раз, блядь. По умолчанию — это просто такая хитрая обёртка над делегатом, который может много подписчиков держать. Называется multicast-делегат, ну, чтоб ты знал. Вызвал событие — и понеслась, все обработчики, которые на него подписались, начинают по очереди отрабатывать, как в очереди за водкой. Кто первый пришёл, того и тапки.

Что важно, а то потом будешь головой об стенку биться:

  • Порядок вызова: Как подписались через +=, в таком порядке их и вызовут. Первый пришёл — первый ушёл, всё честно.
  • Исключения: Вот тут, блядь, самое интересное. Если один из этих обработчиков возьмёт и выкинет исключение — всё, пиздец, остальные просто не отработают. Цепочка рвётся, как гнилая нитка. Для серьёзных систем это вообще жесть, надо всегда помнить.
  • Потокобезопасность: Операции += и -= сами по себе атомарные, то есть потокобезопасные. Но если у тебя одновременно десять потоков лезут подписываться и вызывать событие — тут уже надо головой думать и синхронизацию городить, иначе будет каша, хуле.
  • Утечки памяти: Классика жанра, ебать! Объект подписался и забыл отписаться. А издатель-то его держит крепко, как родного. И сидит твой объект в памяти, как призрак, пока сборщик мусора не заплачет. Всегда отписывайся через -=, когда объект больше не нужен, это святое.

Ну, смотри, как обычно это выглядит на практике:

public class Publisher
{
    public event EventHandler<EventArgs> OnDataProcessed;

    public void ProcessData()
    {
        // Тут что-то делаем умное...
        OnDataProcessed?.Invoke(this, EventArgs.Empty); // И бабахаем всем подписчикам!
    }
}

public class Subscriber
{
    public void Subscribe(Publisher pub)
    {
        pub.OnDataProcessed += HandleNotification;
    }

    private void HandleNotification(object sender, EventArgs e)
    {
        Console.WriteLine("Данные обработаны, ура.");
    }
}

А если хочешь поумничать и своё поведение прикрутить? Ну, например, логировать, кто подписался, или сделать так, чтобы обработчики все до одного отработали, даже если один сдох. Для этого есть кастомные аксессоры add и remove. Выглядит, конечно, как шаманство, но работает:

private EventHandler _customEvent;
public event EventHandler CustomEvent
{
    add
    {
        Console.WriteLine($"Добавлен обработчик: {value.Method.Name}");
        _customEvent += value;
    }
    remove
    {
        Console.WriteLine($"Удалён обработчик: {value.Method.Name}");
        _customEvent -= value;
    }
}

Вот и вся магия. Главное — не накосячить с исключениями и не породить утечек, а то потом будешь искать, куда память девается, ебать колотить.