Вызывать события C # с помощью метода расширения - это плохо? - PullRequest
49 голосов
/ 24 октября 2008

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

public event EventHandler SomethingHappened;
protected virtual void OnSomethingHappened(EventArgs e)
{            
    var handler = SomethingHappened;
    if (handler != null)
        handler(this, e);
}

Недавно в каком-то другом вопросе на этой доске (который я не могу найти сейчас) кто-то указал, что в этом сценарии можно было бы красиво использовать методы расширения. Вот один из способов сделать это:

static public class EventExtensions
{
    static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
    {
        var handler = @event;
        if (handler != null)
            handler(sender, e);
    }
    static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e)
        where T : EventArgs
    {
        var handler = @event;
        if (handler != null)
            handler(sender, e);
    }
}

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

public event EventHandler SomethingHappened;

void SomeMethod()
{
    this.SomethingHappened.RaiseEvent(this, EventArgs.Empty);
}

Мой вопрос: это хорошая идея? Мы что-то упускаем из-за отсутствия стандартного метода On? (Одна вещь, которую я заметил, это то, что она не работает с событиями, которые имеют явный код добавления / удаления.)

Ответы [ 5 ]

56 голосов
/ 24 октября 2008

Он по-прежнему будет работать с событиями, которые имеют явное добавление / удаление - вам просто нужно использовать переменную делегата (или как вы сохранили делегат) вместо имени события.

Однако есть более простой способ сделать его поточно-ориентированным - инициализировать его с помощью неоперативного обработчика:

public event EventHandler SomethingHappened = delegate {};

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

Кстати, в вашем методе расширения вам не нужна дополнительная локальная переменная - вы можете просто сделать:

static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
{
    if (@event != null)
        @event(sender, e);
}

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

Лично я бы не использовал ключевое слово в качестве имени параметра, но оно на самом деле не меняет вызывающую сторону, поэтому делайте что хотите:)

РЕДАКТИРОВАТЬ: Что касается метода "OnXXX": планируете ли вы получать ваши классы из? На мой взгляд, большинство классов должны быть запечатаны. Если вы делаете , хотите ли вы, чтобы эти производные классы могли вызвать событие? Если ответ на любой из этих вопросов «нет», не беспокойтесь. Если ответ на оба вопроса "да", тогда делайте:)

9 голосов
/ 14 августа 2015

Теперь C # 6 здесь, есть более компактный, потокобезопасный способ вызвать событие:

SomethingHappened?.Invoke(this, e);

Invoke() вызывается только в том случае, если для события зарегистрированы делегаты (т. Е. Оно не равно нулю), благодаря условно-нулевому оператору "?".

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

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

5 голосов
/ 24 октября 2008

[Вот мысль]

Просто напишите код один раз рекомендованным способом и покончите с этим. Тогда вы не будете путать своих коллег, просматривая код, думая, что вы сделали что-то не так?

[Я прочитал больше постов, пытаясь найти способы написания обработчика событий, чем я когда-либо тратил на написание обработчика событий.]

3 голосов
/ 24 октября 2008

Меньше кода, более читабельно. Мне нравится.

Если вы не заинтересованы в производительности, вы можете объявить свое событие следующим образом, чтобы избежать проверки на ноль:

public event EventHandler SomethingHappened = delegate{};
1 голос
/ 24 октября 2008

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

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

Другое дело, что для незапечатанных классов метод OnFoo должен быть виртуальным, что я не считаю возможным с помощью методов расширения.

...