Какой шаблон C # имеет лучшую производительность, чтобы избежать дублирования обработчиков событий?

В основном существует два шаблона, позволяющих избежать дублирования регистрации обработчиков событий: (Согласно этому обсуждению: шаблон C # для предотвращения перехвата обработчика событий дважды)

  1. Используя пространство имен System.Linq, и проверьте, зарегистрирован ли обработчик событий, вызвав GetInvocationList().Contains(MyEventHandlerMethod);

  2. Отмените регистрацию перед регистрацией, например:

    MyEvent -= MyEventHandlerMethod;
    MyEvent += MyEventHandlerMethod;
    

Мой вопрос: с точки зрения производительности, какой из них лучше, или между ними есть значительная разница в производительности?


person RainCast    schedule 30.12.2014    source источник
comment
Серьезно, эти операции выполняются очень быстро, и если у вас нет многих тысяч обработчиков событий, от такого рода оптимизации производительности не будет никакой практической пользы. Есть ли у вас реальная ситуация, когда это проблема?   -  person Enigmativity    schedule 30.12.2014
comment
Вы говорите о наносекундах, нет никакой разницы, просто проверка условий, вам нужен миллион операций, чтобы увидеть такую ​​разницу.   -  person oussama abdou    schedule 30.12.2014
comment
Я не могу дать четкого ответа, но я бы предположил, что Contains(), потому что +=/-= внутренне все равно нужно будет перебирать список вызовов, а затем дважды манипулировать им. Но, как говорит Enigmativity, вы вряд ли попадете в ситуацию, когда это будет иметь какое-либо значение.   -  person Rhumborl    schedule 30.12.2014
comment
@Rhumborl Linq также имеет накладные расходы, и, как правило, вы можете легко увидеть снижение производительности в узких циклах, где linq выполняет итерацию по небольшим коллекциям. Но, вероятно, нет способа сказать, что быстрее, без надлежащего теста.   -  person luk32    schedule 30.12.2014
comment
Измеряйте и знайте. Не гадайте и не спрашивайте предположений. В первую очередь побеспокойтесь об остальной части вашего кода, есть вероятность, что придется вести более крупные / худшие / более сложные битвы с более высокой отдачей от инвестиций.   -  person Emond Erno    schedule 30.12.2014
comment
.NET Framework сильно вкладывается в борьбу с подобными паттернами. Предоставляя событиям добавление / удаление средств доступа, чтобы вы не могли получить список вызовов. В первую очередь потому, что это вообще не шаблон, это ошибка. Намеренное сокрытие ошибок в клиентском коде или двойное угадывание того, что клиентский код намеревался, когда он подписывается на события, является ошибкой.   -  person Hans Passant    schedule 30.12.2014
comment
Если бы (1) было быстрее, компилятор C # просто испустил бы (1), если бы вы написали (2).   -  person usr    schedule 30.12.2014


Ответы (4)


Согласно документации, список вызовов сохраняется в виде массива или что-то подобное, и порядок обработчиков событий тоже сохраняется. Может быть, там есть внутренняя структура для поддержки быстрого поиска того или иного метода.

Таким образом, в худшем случае операция GetInvocationList().Contains(MyEventHandlerMethod); равна O(1) (поскольку мы просто получили ссылку на массив) + O(n) для поиска метода, даже если для него нет оптимизации. Я серьезно сомневаюсь, что это правда, и думаю, что есть некоторый оптимизирующий код, и это O(log_n).

Во втором подходе есть дополнительная операция добавления, которая, я думаю, O(1), поскольку мы добавляем обработчик событий в конец.

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

person VMAtm    schedule 30.12.2014

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

И GetInvocationList, и -= проходят по внутреннему массиву _invocationList. (См. исходный код)

Метод расширения LINQ Contains займет больше времени, так как он требует, чтобы весь массив был пройден и преобразован, возвращен и затем проверен самим Contains. Contains имеет то преимущество, что не нужно добавлять обработчик событий, если он существует, что будет означать некоторый прирост производительности.

person Patrick Hofman    schedule 30.12.2014

  1. не будет работать для внешних абонентов и не очень эффективен в любом случае
  2. должно быть в порядке (обратите внимание, что он создает 2 экземпляра делегата каждый раз), но также рассмотрите
  3. в большинстве сценариев должно быть легко узнать, подписаны ли вы уже; если вы не можете знать, то это указывает на архитектурную проблему

Типичным использованием будет «подписка {некоторое использование} [отмена подписки]», где отмена подписки может не потребоваться, в зависимости от относительного времени жизни издателя и подписчика события; если у вас действительно есть сценарий повторного входа, то "подписаться, если еще не подписаны" само по себе проблематично, потому что при отмене подписки позже вы не знаете, предотвращаете ли вы внешнюю итерацию, получающую событие .

person Marc Gravell    schedule 30.12.2014
comment
Для точности во многих случаях вы можете добавить обработчик в код, который должен запускаться только один раз (конструктор, обработчик нагрузки), и удалить его в методе Dispose ... - person Phil1970; 09.09.2016

MyEvent -= MyEventHandlerMethod сначала необходимо найти зарегистрированный обработчик событий в списке вызовов, чтобы удалить его. Так что GetInvocationList().Contains лучше, но это действительно несущественно.

Но обратите внимание, что вы не можете получить доступ к списку вызовов event EventHandler foo ....

person baryo    schedule 30.12.2014
comment
обратите внимание, что вы не можете получить доступ к списку вызовов EventHandler foo ›вы можете, если объект владеет обработчиком событий. - person Patrick Hofman; 30.12.2014