Есть ли недостаток в добавлении анонимного пустого делегата в объявление события?

Я видел несколько упоминаний об этой идиоме (в том числе о SO ):

// Deliberately empty subscriber public event EventHandler AskQuestion = delegate {}; 

Потенциал роста ясен – он избегает необходимости проверять значение null до начала события.

Тем не менее, я очень хочу понять, есть ли недостатки. Например, это то, что широко используется и достаточно прозрачно, чтобы оно не вызывало головной боли обслуживания? Есть ли заметный выигрыш в звонке на абонент?

Единственный недостаток – очень небольшое снижение производительности, поскольку вы вызываете лишний пустой делегат. Помимо этого не существует штрафа за обслуживание или другого недостатка.

Вместо того, чтобы вызывать накладные расходы на производительность, почему бы не использовать метод расширения для решения обеих проблем:

 public static void Raise(this EventHandler handler, object sender, EventArgs e) { if(handler != null) { handler(sender, e); } } 

После того, как вы определили, вам больше не нужно будет выполнять еще одну проверку события:

 // Works, even for null events. MyButtonClick.Raise(this, EventArgs.Empty); 

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

Вот несколько цифр, на которых работают тесты на моей машине:

 For 50000000 iterations . . . No null check (empty delegate attached): 530ms With null check (no delegates attached): 249ms With null check (with delegate attached): 452ms 

И вот код, который я использовал для получения этих цифр:

 using System; using System.Diagnostics; namespace ConsoleApplication1 { class Program { public event EventHandler EventWithDelegate = delegate { }; public event EventHandler EventWithoutDelegate; static void Main(string[] args) { //warm up new Program().DoTimings(false); //do it for real new Program().DoTimings(true); Console.WriteLine("Done"); Console.ReadKey(); } private void DoTimings(bool output) { const int iterations = 50000000; if (output) { Console.WriteLine("For {0} iterations . . .", iterations); } //with anonymous delegate attached to avoid null checks var stopWatch = Stopwatch.StartNew(); for (var i = 0; i < iterations; ++i) { RaiseWithAnonDelegate(); } stopWatch.Stop(); if (output) { Console.WriteLine("No null check (empty delegate attached): {0}ms", stopWatch.ElapsedMilliseconds); } //without any delegates attached (null check required) stopWatch = Stopwatch.StartNew(); for (var i = 0; i < iterations; ++i) { RaiseWithoutAnonDelegate(); } stopWatch.Stop(); if (output) { Console.WriteLine("With null check (no delegates attached): {0}ms", stopWatch.ElapsedMilliseconds); } //attach delegate EventWithoutDelegate += delegate { }; //with delegate attached (null check still performed) stopWatch = Stopwatch.StartNew(); for (var i = 0; i < iterations; ++i) { RaiseWithoutAnonDelegate(); } stopWatch.Stop(); if (output) { Console.WriteLine("With null check (with delegate attached): {0}ms", stopWatch.ElapsedMilliseconds); } } private void RaiseWithAnonDelegate() { EventWithDelegate(this, EventArgs.Empty); } private void RaiseWithoutAnonDelegate() { var handler = EventWithoutDelegate; if (handler != null) { handler(this, EventArgs.Empty); } } } } 

Если вы делаете это a / lot /, вы можете захотеть иметь один, статический / общий пустой делегат, который вы повторно используете, просто для уменьшения объема экземпляров делегата. Обратите внимание, что компилятор кэширует этот делегат на событие в любом случае (в статическом поле), поэтому это только один экземпляр делегата для определения события, поэтому он не является большой экономией, но, возможно, стоит.

Конечно, поле каждого экземпляра в каждом classе все равно будет занимать одно и то же пространство.

т.е.

 internal static class Foo { internal static readonly EventHandler EmptyEvent = delegate { }; } public class Bar { public event EventHandler SomeEvent = Foo.EmptyEvent; } 

Кроме этого, это кажется прекрасным.

Я понимаю, что пустой делегат является streamобезопасным, а нулевая проверка – нет.

Я бы сказал, что это немного опасная конструкция, потому что она соблазняет вас сделать что-то вроде:

 MyEvent(this, EventArgs.Empty); 

Если клиент выдает исключение, сервер идет с ним.

Итак, возможно, вы это сделаете:

 try { MyEvent(this, EventArgs.Empty); } catch { } 

Но, если у вас несколько подписчиков, а один абонент выбрасывает исключение, что происходит с другими абонентами?

С этой целью я использовал некоторые статические вспомогательные методы, которые выполняют нулевую проверку и проглатывают любое исключение со стороны абонента (это от имени).

 // Usage EventHelper.Fire(MyEvent, this, EventArgs.Empty); public static void Fire(EventHandler del, object sender, EventArgs e) { UnsafeFire(del, sender, e); } private static void UnsafeFire(Delegate del, params object[] args) { if (del == null) { return; } Delegate[] delegates = del.GetInvocationList(); foreach (Delegate sink in delegates) { try { sink.DynamicInvoke(args); } catch { } } } 

Существует никаких значимых штрафов за производительность, о которых можно говорить, за исключением, возможно, некоторых экстремальных ситуаций.

Обратите внимание, однако, что этот трюк становится менее актуальным в C # 6.0, потому что язык предоставляет альтернативный синтаксис для вызова делегатов, который может иметь значение null:

 delegateThatCouldBeNull?.Invoke(this, value); 

Выше, нулевой условный оператор ?. сочетает проверку нуля с условным вызовом.

Вместо подхода «пустой делегат» можно определить простой метод расширения, чтобы инкапсулировать традиционный метод проверки обработчика события против нуля. Это описано здесь и здесь .

Одна вещь упущена в качестве ответа для этого вопроса: опасно избегать проверки нулевого значения .

 public class X { public delegate void MyDelegate(); public MyDelegate MyFunnyCallback = delegate() { } public void DoSomething() { MyFunnyCallback(); } } X x = new X(); x.MyFunnyCallback = delegate() { Console.WriteLine("Howdie"); } x.DoSomething(); // works fine // .. re-init x x.MyFunnyCallback = null; // .. continue x.DoSomething(); // crashes with an exception 

Дело в том, что вы никогда не знаете, кто будет использовать ваш код. Вы никогда не знаете, если через несколько лет при исправлении ошибки ваш код event / обработчик установлен в null.

Всегда записывайте чек.

Надеюсь, это поможет 😉

ps: Спасибо за расчет производительности.

pps: Отредактировано это из примера события и обратного вызова. Спасибо за отзывы … Я «закодировал» пример без Visual Studio и скорректировал пример, который я имел в виду для события. Извините за путаницу.

ppps: Не знаю, все ли это подходит для streamа … но я думаю, что это важный принцип. Также проверьте еще один stream стека

Interesting Posts

Как получить RouteData по URL?

Динамическое изменение fragmentов внутри узла вкладки fragmentов?

Измените имя exe в зависимости от условного символа компиляции

C ++ lambda с захватами как указатель функции

Лучший способ запросить страницу данных и получить общее количество в инфраструктуре сущностей 4.1?

ListFragment не принимает мой макет

Вставить многострочную строку Java в Eclipse

Создание виртуального изображения физического раздела

Список и выбор точек доступа WLAN (базовых станций) в Mac OS X

Возможно ли «вернуться» в GRUB после загрузки Linux без перезагрузки

неclassовые значения всегда имеют cv-неквалифицированные типы

Windows 7 работает медленно после возобновления спячки

IntelliJ не может запускать простое веб-приложение: невозможно подключиться к серверу ping на localhost: 1099

Когда использовать обратный вызов React setState

Установка Windows без графической карты

Давайте будем гением компьютера.