В чем разница event Action и Action в C#?
Что проверяют
Понимаете ли вы, что event это не отдельный тип, а модификатор, который ограничивает доступ к делегату снаружи класса. Следом спрашивают, во что компилятор превращает событие и почему наследник не может вызвать событие базового класса.
Как отвечать
Action это обычный делегат из System: метод без возвращаемого значения, в обобщённых версиях до 16 параметров. Публичное поле public Action Changed; открывает делегат целиком, и любой внешний код может:
- вызвать его:
obj.Changed(), то есть поднять событие вместо владельца; - перезаписать через
=:obj.Changed = Handlerмолча удалит всех остальных подписчиков; - обнулить:
obj.Changed = null.
Объявление public event Action Changed; компилятор разворачивает в приватное поле делегата и пару открытых методов доступа add и remove. Снаружи класса доступны только += и -=, а вызывать и присваивать можно лишь внутри объявившего класса. Поэтому наследнику дают защищённый метод вроде protected virtual void OnChanged(), который поднимает событие.
Дополните деталями:
- событие можно объявить в интерфейсе, а методы доступа написать вручную через
add { } remove { }, например чтобы хранить подписчиков в своей коллекции; - вызов пишут как
Changed?.Invoke(): делегат неизменяем, значение читается один раз, и отписка из другого потока между проверкой и вызовом не приведёт кNullReferenceException; - в .NET принято
EventHandler<TEventArgs>с отправителем и аргументами, аActionчаще встречается в Unity и во внутреннем коде.
Где спотыкаются
Называют событие «специальным типом делегата». Тип остаётся тем же, event меняет только доступ к члену класса.
Думают, что событие защищает от утечек. Издатель держит ссылку на подписчика, и без -= подписчик не соберётся сборщиком мусора, пока жив издатель. В Unity отписку обычно ставят в OnDisable.
Не знают, что исключение в одном обработчике прерывает вызов остальных. Если это важно, обходят GetInvocationList() и вызывают каждый обработчик в своём try.