В чем разница между делегатом и событием (event) в C#?

Ответ

Делегат — это тип, представляющий ссылку на метод. Событие — это конструкция языка, построенная на основе делегата, которая добавляет строгую инкапсуляцию для реализации шаблона «издатель-подписчик».

Делегат (Delegate)

  • Это типобезопасный указатель на функцию.
  • Экземпляр делегата можно создавать, передавать как параметр, вызывать.
  • Нет встроенных ограничений: любой код с доступом к делегату может его вызвать, перезаписать или очистить список вызовов.

Событие (Event)

  • Это обёртка над делегатом, которая ограничивает операции с ним извне класса-издателя.
  • Реализует принцип инкапсуляции: издатель контролирует момент вызова, подписчики контролируют только подписку/отписку.

Ключевые различия на практике:

public class EventPublisher
{
    // 1. Делегат как публичное поле (плохая практика для шаблона издатель-подписчик)
    public Action? PublicDelegate;

    // 2. Событие, построенное на основе делегата Action
    public event Action? OnSomethingHappened;

    public void DoWork()
    {
        // Вызов возможен и там, и там ВНУТРИ класса-издателя
        PublicDelegate?.Invoke();
        OnSomethingHappened?.Invoke();
    }
}

public class Subscriber
{
    public void Subscribe(EventPublisher publisher)
    {
        // РАБОТАЕТ: Подписка на событие (только +=, -=)
        publisher.OnSomethingHappened += HandleEvent;

        // РАБОТАЕТ: Подписка на делегат
        publisher.PublicDelegate += HandleEvent;

        // ОШИБКА КОМПИЛЯЦИИ: Нельзя вызвать событие извне класса-издателя.
        // publisher.OnSomethingHappened?.Invoke();

        // РАБОТАЕТ (и это проблема!): Можно вызвать делегат извне, нарушив инкапсуляцию.
        publisher.PublicDelegate?.Invoke();

        // ОШИБКА КОМПИЛЯЦИИ: Нельзя перезаписать всех подписчиков события.
        // publisher.OnSomethingHappened = null;

        // РАБОТАЕТ (и это большая проблема!): Можно удалить ВСЕХ подписчиков делегата.
        publisher.PublicDelegate = null;
    }

    private void HandleEvent() => Console.WriteLine("Обработка.");
}
Сводная таблица: Операция Делегат (поле) Событие (event)
Вызов извне класса-издателя Разрешен (del.Invoke()) Запрещен (только внутри класса)
Подписка (+=) Разрешена Разрешена
Отписка (-=) Разрешена Разрешена
Полная перезапись (=) Разрешена (del = null) Запрещена (только в классе-издателе)
Цель Общий механизм обратных вызовов Безопасная реализация шаблона «издатель-подписчик»

Вывод: Используйте event, когда нужно уведомлять внешние объекты о изменениях в вашем классе. Это защищает инварианты класса и предотвращает случайное или злонамеренное вмешательство в логику уведомлений.

Ответ 18+ 🔞

Давай разберём эту дичь на пальцах, а то в учебниках пишут так, что мозг вытекает. Представь себе делегат — это как громкая связь в чатике. Кто угодно может в неё крикнуть, а кто угодно может её вообще вырубить или переключить на другую беседу. Полный бардак, да?

А событие — это уже не просто громкая связь, а нормальный, ебаный, модераторский чат. Ты как пользователь можешь только подписаться на уведомления (+=) или отписаться (-=). А вот орать в общий чат или банить всех подписчиков — это уже право модератора, то есть класса-издателя. Тебе, снаружи, эту кнопку не дадут.

Смотри, как это выглядит в коде, на живом примере:

public class EventPublisher
{
    // 1. Публичный делегат — это просто поле, открытое всем на растерзание.
    public Action? PublicDelegate;

    // 2. А вот это уже событие. Тот же делегат, но в бронежилете.
    public event Action? OnSomethingHappened;

    public void DoWork()
    {
        // Внутри класса-издателя можно вызывать и то, и другое. Всё своё.
        PublicDelegate?.Invoke();
        OnSomethingHappened?.Invoke();
    }
}

public class Subscriber
{
    public void Subscribe(EventPublisher publisher)
    {
        // Так, подписываемся на событие. Нормально, законно.
        publisher.OnSomethingHappened += HandleEvent;

        // И на публичный делегат тоже можно подписаться.
        publisher.PublicDelegate += HandleEvent;

        // А ТУТ НАЧИНАЕТСЯ МАГИЯ, БЛЯДЬ:

        // ЭТО НЕ СКОМПИЛИРУЕТСЯ! Нельзя просто так взять и вызвать событие снаружи.
        // publisher.OnSomethingHappened?.Invoke(); // ОШИБКА!

        // А ВОТ ЭТО — ЗАКОМПИЛИТСЯ! И это пиздец как плохо.
        // Любой левый код может накликать уведомление, когда ему вздумается.
        publisher.PublicDelegate?.Invoke();

        // И ЭТО ТОЖЕ ОШИБКА! Нельзя снаружи обнулить всех подписчиков события.
        // publisher.OnSomethingHappened = null; // ОШИБКА!

        // А ВОТ ЭТО — ПОЖАЛУЙСТА! Один мудак может сделать `= null` и сотрёт ВСЕ подписки.
        publisher.PublicDelegate = null;
    }

    private void HandleEvent() => Console.WriteLine("Обработка.");
}

Короче, резюмирую, чтобы вообще ни у кого вопросов не осталось:

Что хочешь сделать С публичным делегатом (поле) С событием (event)
Крикнуть всем снаружи Да, легко (del.Invoke()) Низя! Только внутри класса
Подписаться (+=) Да Да
Отписаться (-=) Да Да
Обнулить всех (= null) Да, и это пиздец Низя! Только внутри класса
Суть Просто указатель на метод Защищённая реализация "издатель-подписчик"

Вывод, ёпта: Если делаешь класс, который должен уведомлять других о своих внутренних делах — всегда юзай event. Иначе какой-нибудь распиздяй в другом модуле вызовет твой делегат не вовремя или, того хуже, обнулит его, и ты будешь месяц искать, почему подписчики не работают. Событие — это защита от дурака, а чаще — от самого себя через полгода.