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

StackOverflow https://stackoverflow.com/questions/231525

Вопрос

Мы все знакомы с ужасом объявления событий 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? (Одна вещь, которую я заметил, это то, что она не работает с событиями, которые имеют явный код добавления / удаления.)

Это было полезно?

Решение

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

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

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" Метод: планируете ли вы получать уроки из? На мой взгляд, большинство классов должны быть запечатаны. Если вы делаете , хотите ли вы, чтобы эти производные классы могли вызывать событие? Если ответ на один из этих вопросов "нет" тогда не беспокойся. Если ответом на оба вопроса является "да" тогда делай :)

Другие советы

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

SomethingHappened?.Invoke(this, e);

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

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

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

[Вот мысль]

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

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

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

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

public event EventHandler SomethingHappened = delegate{};

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

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

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

scroll top