У события должен быть хотя бы один обработчик? - PullRequest
2 голосов
/ 07 августа 2010

Почему событие должно иметь хотя бы один обработчик?

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

this.MyCustomEvent(this, someArgs);

оно выдает NullReferenceException, если на него не подписан обработчик,

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

this.MyCustomEvent += myCutomEventHandler;

void myCustomEventHandler(object sender, EventArgs e)
{ /* do nothing */ }

Это нормально, или я что-то не так делаю?Разве это не должно проверять автоматически, есть ли какие-нибудь обработчики, подписанные?Это немного глупо, ИМХО.

Ответы [ 7 ]

4 голосов
/ 07 августа 2010

Я рекомендую вам использовать чрезвычайно полезный метод расширения:

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

, который сделает проверку за вас.

Использование:

MyCustomEvent.Raise(this, EventArgs.Empty);
4 голосов
/ 07 августа 2010

Обратите внимание, что делегаты являются ссылочными типами, и их значение по умолчанию равно нулю.

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

EventHandler myCustomEventCopy = MyCustomEvent;

if (myCustomEventCopy != null)
{
    myCustomEventCopy (this, someArgs);
}

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

Одним из решений, которое решает обе проблемы, является инициализация событий пустым обработчиком, например

public event EventHandler MyCustomEvent = delegate { };

и затем запустить их без каких-либо проверок, например

MyCustomEvent(this, someArgs);

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

http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx

Липперт отмечает, что для полного устранения проблемы «обработчик срабатывает после отмены регистрации» необходимо, чтобы сами обработчики 1020 * были написаны надежным образом.

2 голосов
/ 07 августа 2010

Это нормальное поведение.Обычный шаблон для генерирования события в .NET состоит в том, чтобы иметь метод с именем OnMyCustomEvent, который вы используете для выброса события, например так:

    protected void OnMyCustomEvent(MyCustomEventArgs e)
    {
        EventHandler<MyCustomEventArgs> threadSafeCopy = MyCustomEvent;
        if (threadSafeCopy != null)
            threadSafeCopy(this, e);
    }

    public event EventHandler<MyCustomEventArgs> MyCustomEvent;

Затем из вашего кода вы бы вызвали this.OnMyCustomEvent(someArgs)вместо этого.

2 голосов
/ 07 августа 2010

Событие внизу равно MulticastDelegate, что равно нулю, если в списке вызовов нет метода.Обычно вы используете метод RaiseEvent() для вызова события, шаблон выглядит следующим образом:

public void RaiseEvent()
{
  var handler = MyEvent;
  if(handler != null)
    handler(this, new EventArgs());
}

Когда вы присваиваете событие переменной, это потокобезопасно.Однако вы можете пропустить удаленный или добавленный метод, который был добавлен между атомарными операциями (присваивание -> проверка нуля -> вызов).

1 голос
/ 07 августа 2010

Существует "стандартный" подход: копировать ссылку, а затем проверять на нулевое значение (значение ссылки, если обработчики не прикреплены) - как указано в других ответах:

public EventHandler<MyEventArgs> MyEvent;

protected virtual OnMyEvent(MyEventArgs args) {
  var copy = MyEvent;
  if (copy != null) {
    copy(this, args);
  }
}

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

Существует еще один подход: шаблон нулевого объекта:

public EventHandler<MyEventArgs> MyEvent;

// In constructor:
  MyEvent += (s,e) => {};   // No op, so it is always initialised

И нет необходимости брать копию или проверять на ноль, потому что это не будет. Это работает, потому что в event.

нет метода Clear.
1 голос
/ 07 августа 2010

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

if (MyCustomEvent != null)
{
    MyCustomEvent(this, someArgs);
}
0 голосов
/ 07 августа 2010

Да, вам нужно проверить на ноль.На самом деле, есть правильный способ сделать это:

var myEvent = MyCustomEvent;
if (myEvent != null)
    myEvent(this, someArgs);

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...