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


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

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

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

MyEvent( param1, param2 );

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

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

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

8 65

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 нет необходимости переходить на любую из этих длин, чтобы выполнить проверку null, благодаря условному оператору null ?.

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

короче:

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, чтобы на время сборки добавить эту магию. Это самый лучший способ.