Event и delegate: в чем отличие?

277
25 октября 2021, 10:10

Для того чтобы лучше понять хотел знать чем отличается event от delegate и ещё применение операторов += и -= для методов в C#

Answer 1

Давайте начнём с того, что event и delegate — это две разных вещи, настолько же разные, как автомобиль и морковка. Разница между полем-делегатом и event'ом примерно такая же, как между полем и свойством: event иногда выглядит как делегатное поле. Давайте разберёмся в этом.

delegate — это класс, содержащий в себе «шаблон» метода, то есть, сигнатуру метода. Переменная делегатного типа — объект типа MulticastDelegate (точнее, производного от него), который может содержать один или несколько объектов, представляющих собой методы с совместимой с «шаблоном» сигнатурой (контр- и ковариантность немного усложняет картину). То есть это как бы переменная, которая может содержать функции. Для таких переменных определена операция +, которая комбинирует слагаемые-функции в одну новую функцию, и симметричная операция -. Эти операции автоматически порождают производные операции += и -=.

event же — это просто пара методов в классе, обозначаемых как add и remove, и имеющих произвольную семантику, выбранную программистом. (Аналог — геттер и сеттер свойства.) В имплементации по умолчанию для event'а заводится скрытое поле делегатного типа, а add/remove добавляют или убирают из него методы (под lock'ом). (Чтобы немного запутать картину, это скрытое поле доступно по тому же имени, что и event.) Функции add/remove, составляющие event, вызываются соответственно как += и -=. Никаких операций +/-, разумеется, нету.

Давайте ещё пробежимся по отличию event'а в классе от публичного поля делегатного типа.

Рассмотрим случай, когда event реализован «по умолчанию», то есть, с неявным делегатным полем. Отличие состоит в том, что:

  1. Для делегатного поля у вас полный доступ к нему. Вы можете — также и снаружи класса! — разобрать MulticastDelegate на части и собрать новый, вы можете заменить его на свой или присвоить ему null, вы можете его вызвать, можете его скопировать себе в переменную. У вас есть полный доступ, как и к любому публичному полю. (Это, разумеется, вопиющим образом нарушает инкапсуляцию.)

    Для event'а, вы можете лишь написать instance.Event += handler и instance.Event -= handler, что отображается на функции add и remove, которые в свою очередь снова-таки вызывают += и -= для автоматически реализованного делегата. Никакого другого доступа у вас нету. Внутри класса вы, однако, можете получить на чтение значение этого делегата, используя имя event'а. (Это нужно, например, для того, чтобы вызвать этот делегат; VB в отличие от C# имеет специальный метод RaiseEvent с нужными проверками.)

  2. Ещё одно тонкое отличие (уже упомянутое в комментариях к другим ответам) состоит в том, что add/remove вызываются под lock'ом, что делает их потокобезопасными. Однако, потокобезопасность event'ов очень сложна (чуть не сказал «невозможна в принципе») (см. статью [2]), так что эта разница при правильном программировании несущественна: операции с event'ом обычно должны происходить лишь в одном потоке. Заметьте, что lock не доступен вне add/remove, так что вы не сможете скопировать делегат в локальную переменную под ним для потокобезопасного вызова*:

    void Raise()
    {
        EventHandler copy;
        lock (<sorry, not available>)
            copy = MyEvent;
        if (copy != null)
            copy(...);
    }
    

    Впрочем, в современных версиях .NET вместо lock используются Intelocked-операции, и вызов MyEvent?.Invoke(...) потокобезопасен.

Для случая, когда event реализуется не «по умолчанию», общего нет практически ничего. Методы add и remove могут делать всё, что угодно:

int subscriberCount = 0;
public event EventHandler MyEvent
{
    add { subscriberCount++; }
    remove { subscriberCount--; }
}

Программист может завести делегат самостоятельно и «складывать» туда подписанные обработчики, но это в принципе его добрая воля. С другой стороны, всё же рекомендуется не ломать ожидаемую клиентами семантику класса, и использовать event по назначению.

Литература:

  1. Jon Skeet: Delegates and Events
  2. Eric Lippert: Events and races

Кроме того, ключевое слово delegate используется для объявления анонимной функции (более старый параллельный синтаксис для лямбда-выражений).

*В современных версиях C# для потокобезопасного вызова рекомендуется паттерн MyEvent?.Invoke(...). Впрочем, с потокобезопасностью у событий не всё гладко, как объяснено в статье [2].

Answer 2

Главное отличие event от delegate состоит в том, что event может быть запущен только в классе, в котором объявлен. Помимо этого, при наличии event компилятор создает не только соответствующее приватное поле-делегат, но еще и два открытых метода для подписки и ее отмены на события.

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

А вообще по поводу событий и их взаимосвязи с делегатами почитайте у Рихтера, глава 11, которая так и зовется "События"

READ ALSO
Bad quantifier. Unity

Bad quantifier. Unity

Не понимаю в чём проблема , тут нормально ищет

175
Список цифр в textBox или listBox

Список цифр в textBox или listBox

Пишу небольшую прогуСмысл ее такой: есть textBox, в который я вствляю в столбик цифры: целые и с плавающей точкой

237
Как сравнить даты в MS Access?

Как сравнить даты в MS Access?

В таблице столбцы date_in и date_out в виде "1410

164