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

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

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

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

MyEvent( param1, param2 );

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

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

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

8 ответов


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

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");
}

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

(Edit: я получил его с этого поста скрытые возможности C#?)


запись:

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 );

если вы используете C# 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 ); }
}

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

MyEvent += delegate { };

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


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

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);
  }
}

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

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

короче:

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

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

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


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

ваш старый код повышения событий:

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);

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


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