Что бы я потерял, отказавшись от стандартного шаблона EventHandler в.NET?
Существует стандартный шаблон для событий в.NET - они используют delegate
тип, который принимает простой объект с именем sender, а затем фактическую "полезную нагрузку" во втором параметре, который должен быть получен из EventArgs
,
Обоснование второго параметра, полученного из EventArgs
кажется довольно ясным (см. Аннотированный справочник по стандартной библиотеке.NET Framework). Он предназначен для обеспечения двоичной совместимости между приемниками событий и источниками по мере развития программного обеспечения. Для каждого события, даже если оно имеет только один аргумент, мы извлекаем собственный класс аргументов события, который имеет одно свойство, содержащее этот аргумент, так что мы сохраняем возможность добавлять дополнительные свойства к полезной нагрузке в будущих версиях, не нарушая существующий клиентский код, Очень важно в экосистеме самостоятельно разработанных компонентов.
Но я считаю, что то же самое касается нулевых аргументов. Это означает, что если у меня есть событие, которое не имеет аргументов в моей первой версии, и я пишу:
public event EventHandler Click;
... тогда я делаю это неправильно. Если я изменю тип делегата в будущем на новый класс в качестве его полезной нагрузки:
public class ClickEventArgs : EventArgs { ...
... Я нарушу бинарную совместимость с моими клиентами. Клиент в конечном итоге привязан к определенной перегрузке внутреннего метода add_Click
это занимает EventHandler
и если я изменю тип делегата, они не смогут найти эту перегрузку, поэтому есть MissingMethodException
,
Хорошо, а что если я использую удобную универсальную версию?
public EventHandler<EventArgs> Click;
Нет, все еще не так, потому что EventHandler<ClickEventArgs>
это не EventHandler<EventArgs>
,
Таким образом, чтобы получить выгоду от EventArgs
, вы должны извлечь из него, а не использовать его непосредственно как есть. Если вы этого не сделаете, вы можете не использовать его (мне кажется).
Тогда есть первый аргумент, sender
, Мне кажется это как рецепт нечестивой связи. Событие запуска по сути является вызовом функции. Должна ли функция, вообще говоря, иметь возможность копаться в стеке и выяснять, кто был вызывающим абонентом, и соответствующим образом корректировать свое поведение? Должны ли мы сделать так, чтобы интерфейсы выглядели так?
public interface IFoo
{
void Bar(object caller, int actualArg1, ...);
}
Ведь разработчик Bar
может захотеть узнать кто caller
был, поэтому они могут запросить дополнительную информацию! Я надеюсь, что ты сейчас вырвался. Почему это должно быть иначе для событий?
Так что, даже если я готов взять на себя труд сделать автономный EventArgs
класс для каждого события, которое я объявляю, просто чтобы оно стоило моего использования EventArgs
вообще, я определенно предпочел бы отбросить аргумент отправителя объекта.
Функция автозаполнения в Visual Studio, похоже, не заботится о том, какой делегат вы используете для события - вы можете ввести +=
[нажмите Пробел, Возврат] и он напишет для вас метод-обработчик, который соответствует какому-либо делегату.
Так какое значение я бы потерял, отклонившись от стандартного шаблона?
В качестве дополнительного вопроса, будет ли C#/CLR 4.0 делать что-нибудь, чтобы изменить это, возможно, с помощью контравариантности в делегатах? Я попытался исследовать это, но столкнулся с другой проблемой. Первоначально я включил этот аспект вопроса в этот другой вопрос, но это вызвало путаницу. И, кажется, немного разбить это на три вопроса...
Обновить:
Оказывается, я был прав, задаваясь вопросом о влиянии контравариантности на весь этот вопрос!
Как уже отмечалось, новые правила компилятора оставляют дыру в системе типов, которая взрывается во время выполнения. Отверстие было эффективно закупорено путем определения EventHandler<T>
иначе Action<T>
,
Так что для событий, чтобы избежать этой дыры типа, вы не должны использовать Action<T>
, Это не значит, что вы должны использовать EventHandler<TEventArgs>
; это просто означает, что если вы используете универсальный тип делегата, не выбирайте тот, который включен для контравариантности.
2 ответа
Ничего, ты ничего не теряешь. Я использую Action<>
с тех пор, как вышел.NET 3.5, и с ним гораздо проще работать.
Я даже не имею дело с EventHandler
введите для сгенерированных обработчиков событий больше, просто напишите нужную сигнатуру метода и соедините ее лямбда-выражением:
btnCompleteOrder.OnClick += (o,e) => _presenter.CompleteOrder();
Мне также не нравится шаблон обработчика событий. На мой взгляд, объект Sender не так уж и полезен. В случаях, когда событие говорит о том, что что-то случилось с каким-либо объектом (например, уведомление об изменении), было бы более полезно иметь информацию в EventArgs. Единственное использование, которое я мог любопытно видеть для Отправителя, - это отписаться от события, но не всегда ясно, на какое событие следует отписаться.
Кстати, если бы у меня были мои детекторы, событие не было бы методом AddHandler и методом RemoveHandler; это был бы просто метод AddHandler, который возвращал бы MethodInvoker, который можно использовать для отмены подписки. Вместо аргумента Sender в качестве первого аргумента я бы использовал копию MethodInvoker, необходимую для отмены подписки (в случае, если объект обнаруживает, что получает события, для которых был отменен вызывающий объект отмены подписки). Стандартный MulticastDelegate не подходит для отправки событий (поскольку каждый подписчик должен получить различный делегат отмены подписки), но для отмены подписки не потребуется линейный поиск по списку вызовов.