Автоматически создавать пустые обработчики событий С#

Невозможно запустить событие на С#, у которого нет связанных с ним обработчиков. Поэтому перед каждым вызовом необходимо проверить, является ли событие нулевым.

if ( MyEvent != null ) {
  MyEvent( param1, param2 );
}

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

MyEvent( param1, param2 );

В настоящее время я решаю это, добавляя пустой проводник для каждого события вручную. Это склонно к ошибкам, так как мне нужно помнить об этом и т.д.

void Initialize() {
  MyEvent += new MyEvent( (p1,p2) => { } );
}

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

Ответы

Ответ 1

Я видел это на другом посту и бесстыдно украл его и использовал его в большинстве своих кодов с тех пор:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click = delegate {}; // add empty delegate!

//Let you do this:
public void DoSomething() {
    Click(this, "foo");
}

//Instead of this:
public void DoSomething() {
    if (Click != null) // Unnecessary!
        Click(this, "foo");
}

* Если кто-то знает происхождение этой техники, отправьте ее в комментариях. Я действительно верю, что источник получает должный кредит.

(Изменить: Я получил его из этого сообщения Скрытые возможности С#?)

Ответ 2

Обозначение:

if ( MyEvent != null ) {
  MyEvent( param1, param2 );
}

не является потокобезопасным. Вы должны сделать это следующим образом:

EventHandler handler = this.MyEvent;
if ( null != handler ) { handler( param1, param2 ); }

Я понимаю, что это беспокоит, поэтому вы можете использовать вспомогательный метод:

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

а затем вызовите:

RaiseEvent( MyEvent, param1, param2 );

Если вы используете С# 3.0, вы можете объявить вспомогательный метод как метод расширения:

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

а затем вызовите:

MyEvent.Raise( param1, param2 );

Также вы можете создать следующие методы расширения/помощника для других обработчиков событий. Например:

static void Raise<TEventArgs>( this EventHandler<TEventArgs> handler,
    object sender, TEventArgs e ) where TEventArgs : EventArgs
{
    if ( null != handler ) { handler( sender, e ); }
}

Ответ 3

Вы можете написать так:

MyEvent += delegate { };

Я не уверен, что вы хотите сделать правильно.

Ответ 4

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

public static class EventHandlerExtensions {
  public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs {
    if (handler != null) handler(sender, args);
  }
}

Ответ 5

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

Ответ 6

Объявление событий С#, к сожалению, включает в себя ряд хорошо известных проблем безопасности и неэффективности. Я разработал ряд методов расширения для делегатов, чтобы безопасно их вызывать, а также для регистрации/отмены регистрации делегатов поточно-безопасным способом.

Ваш старый код для создания событий:

if (someDelegate != null) someDelegate(x, y, z);

Ваш новый код:

someDelegate.Raise(x, y, z);

Ваш старый регистрационный код события:

event Action fooEvent;
...
lock (someDummyObject) fooEvent += newHandler;

Ваш новый код:

Action fooEvent;
...
Events.Add(ref fooEvent, newHandler);

Никакой блокировки не требуется, никакие фиктивные объекты, вставленные компилятором, используемые для блокировки событий.

Ответ 7

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

Документы объясняют, что вызов MyEvent?.Invoke(...) копирует событие во временную переменную, выполняет нулевую проверку, а если не null, вызывает Invoke на временной копии. Это не обязательно поточно-безопасное во всех смыслах, так как кто-то мог добавить новое событие после копии во временную переменную, которая не была бы вызвана. Это гарантирует, что вы не назовете Invoke на нулевое значение.

Короче:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click;

public void DoSomething() {
    Click?.Invoke(this, "foo");
}

Ответ 8

Вы можете использовать PostSharp для добавления времени на добавление этой магии. Это лучший способ.