Подтвердить что ты не робот

Что бы я потерял, отказавшись от стандартного шаблона EventHandler в .NET?

Существует стандартный шаблон для событий в .NET - они используют тип delegate, который принимает простой объект, называемый отправителем, а затем фактическую "полезную нагрузку" во втором параметре, который должен быть получен из EventArgs.

Обоснование второго параметра, полученного из EventArgs, кажется довольно ясным (см. . Стандартная библиотека .NET Framework Annotated Reference). Он предназначен для обеспечения бинарной совместимости между приемниками событий и источниками при разработке программного обеспечения. Для каждого события, даже если у него есть только один аргумент, мы выводим класс аргументов пользовательских событий, у которого есть одно свойство, содержащее этот аргумент, поэтому мы сохраняем возможность добавлять дополнительные свойства в полезную нагрузку в будущих версиях без нарушения существующего кода клиента, Очень важно в экосистеме самостоятельно разработанных компонентов.

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

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 -derived для каждого объявляемого события, просто чтобы сделать его полезным при использовании EventArgs вообще, я определенно предпочел бы отказаться от аргумент отправителя объекта.

Функция автозаполнения Visual Studio, похоже, не заботится о том, какой делегат вы используете для события - вы можете набрать += [hit Space, Return] и написать метод обработчика для вас, который соответствует любому делегату, каким он является.

Итак, какое значение я бы потерял, отклонившись от стандартного шаблона?

Как вопрос с бонусом, будет ли С#/CLR 4.0 что-либо менять, возможно, через контравариантность в делегатах? Я попытался исследовать это, но нажал еще одну проблему. Первоначально я включил этот аспект вопроса в этот другой вопрос, но это вызвало путаницу. И, похоже, немного расщепить это на три вопроса...

Update:

Оказывается, я был прав, чтобы узнать о влиянии контравариантности на весь этот вопрос!

Как отметил в другом месте, новые правила компилятора оставляют дыру в системе типов, которая взрывается во время выполнения. Отверстие эффективно подключено путем определения EventHandler<T> по-разному Action<T>.

Итак, для событий, чтобы избежать этого отверстия, вы не должны использовать Action<T>. Это не значит, что вы должны использовать EventHandler<TEventArgs>; это просто означает, что если вы используете общий тип делегата, не выбирайте тот, который включен для контравариантности.

4b9b3361

Ответ 1

Ничего, вы ничего не теряете. Я использовал Action<>, так как .NET 3.5 вышел, и это намного более естественно и проще программировать.

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

btnCompleteOrder.OnClick += (o,e) => _presenter.CompleteOrder();

Ответ 2

Мне тоже не нравится шаблон обработчика событий. На мой взгляд, объект Sender на самом деле не очень полезен. В случаях, когда событие говорит, что что-то случилось с каким-либо объектом (например, уведомление об изменении), было бы более полезно иметь информацию в EventArg. Единственное, что я мог бы сделать для Sender, - это отказаться от подписки на событие, но не всегда понятно, к какому событию следует отказаться от подписки.

Кстати, если бы у меня были мои druthers, Событие не было бы методом AddHandler и методом RemoveHandler; это будет просто метод AddHandler, который возвращает MethodInvoker, который можно использовать для отмены подписки. Вместо аргумента Sender у меня должен быть первый аргумент - это копия MethodInvoker, требуемая для отмены подписки (в случае, если объект обнаруживает, что принимает события, в которые был утерян invubscribe invoker). Стандартный MulticastDelegate не подходит для диспетчеризации событий (так как каждый абонент должен получить другого делегата от подписки), но отмена подписки на события не потребует линейного поиска через список вызовов.