Почему в обработчике событий C # параметр «отправитель» должен быть объектом? - PullRequest
69 голосов
/ 17 сентября 2009

Согласно Руководству по именованию событий Microsoft , параметр sender в обработчике событий C # " всегда объекта типа, даже если возможно использовать более конкретный тип ».

Это приводит к большому количеству кода обработки событий, например:

RepeaterItem item = sender as RepeaterItem;
if (item != null) { /* Do some stuff */ }

Почему конвенция не рекомендует объявлять обработчик событий с более конкретным типом?

MyType
{
    public event MyEventHander MyEvent;
}

...

delegate void MyEventHander(MyType sender, MyEventArgs e);

Я скучаю по Гоче?

Для потомков: я согласен с общим мнением в ответах, что соглашение - это для использования объекта (и для передачи данных через EventArgs), даже когда возможно использование более конкретного типа, и в программирование в реальном мире важно для соблюдения соглашения.

Ответы [ 12 ]

39 голосов
/ 17 сентября 2009

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

Я согласен, что это немного странно, но, вероятно, стоит придерживаться соглашения только для знакомства. (Это знакомство с другими разработчиками.) Я никогда не особо увлекался EventArgs (учитывая, что сам по себе он не передает никакой информации), но это другая тема. (По крайней мере, у нас есть EventHandler<TEventArgs> сейчас - хотя было бы полезно, если бы существовала также EventArgs<TContent> для общей ситуации, когда вам нужно только одно значение для распространения.)

РЕДАКТИРОВАТЬ: Это делает делегат более общего назначения, конечно - один тип делегата может быть повторно использован для нескольких событий. Я не уверен, что покупаю это в качестве особенно веской причины - особенно в свете дженериков - но я думаю, что это что-то ...

17 голосов
/ 17 сентября 2009

Я думаю, что есть веская причина для этого соглашения.

Давайте возьмем (и расширим) пример @ erikkallen:

void SomethingChanged(object sender, EventArgs e) {
    EnableControls();
}
...
MyRadioButton.Click += SomethingChanged;
MyCheckbox.Click += SomethingChanged;
MyDropDown.SelectionChanged += SomethingChanged;
...

Это возможно (и это было с .Net 1, до генериков), потому что поддерживается ковариация.

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

Тем не менее, условием является упрощение написания компонентов. Вы знаете, что для любого события будет работать базовый шаблон (отправитель объекта, EventArgs e).

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

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

//this won't work
GallowayClass.Changed += SomethingChanged;

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

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

Мне нравится идея общих аргументов событий - я уже использую нечто подобное.

9 голосов
/ 17 сентября 2009

Я использую следующий делегат, когда предпочитаю отправителя со строгой типизацией.

/// <summary>
/// Delegate used to handle events with a strongly-typed sender.
/// </summary>
/// <typeparam name="TSender">The type of the sender.</typeparam>
/// <typeparam name="TArgs">The type of the event arguments.</typeparam>
/// <param name="sender">The control where the event originated.</param>
/// <param name="e">Any event arguments.</param>
public delegate void EventHandler<TSender, TArgs>(TSender sender, TArgs e) where TArgs : EventArgs;

Это можно использовать следующим образом:

public event EventHandler<TypeOfSender, TypeOfEventArguments> CustomEvent;
5 голосов
/ 17 сентября 2009

Дженерики и история будут играть большую роль, особенно с количеством элементов управления (и т. Д.), Которые выставляют подобные события. Без генериков вы бы получили множество событий, разоблачающих Control, что в значительной степени бесполезно:

  • вам все еще нужно разыграть, чтобы сделать что-нибудь полезное (кроме проверки ссылок, которую вы можете сделать также с object)
  • вы не можете повторно использовать события на неуправляемых элементах

Если мы рассмотрим дженерики, то опять все хорошо, но вы начинаете сталкиваться с проблемами наследования; если класс B : A, то события A должны быть EventHandler<A, ...>, а события B EventHandler<B, ...>? Опять же, очень запутанно, сложно для инструментов и немного грязно с точки зрения языка.

Пока не найдется лучший вариант, охватывающий все это, object работает; события почти всегда происходят в экземплярах классов, поэтому нет никакого бокса и т. д. - просто приведение. И кастинг не очень медленный.

4 голосов
/ 17 сентября 2009

Я думаю, это потому, что вы должны быть в состоянии сделать что-то вроде

void SomethingChanged(object sender, EventArgs e) {
    EnableControls();
}
...
MyRadioButton.Click += SomethingChanged;
MyCheckbox.Click += SomethingChanged;
...

Почему вы делаете безопасное приведение в своем коде? Если вы знаете, что вы используете функцию только в качестве обработчика событий для ретранслятора, вы знаете, что аргумент всегда имеет правильный тип, и вы можете использовать вместо этого бросающий бросок, например (Repeater) отправитель вместо (отправитель как Repeater).

3 голосов
/ 17 сентября 2009

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

1 голос
/ 17 сентября 2009

Шаблон использования EventHandler (отправитель объекта, EventArgs e) предназначен для предоставления всем событиям средств идентификации источника события (отправителя) и предоставления контейнера для всей конкретной полезной нагрузки события. Преимущество этого шаблона заключается также в том, что он позволяет генерировать несколько различных событий с использованием делегатов одного типа.

Что касается аргументов этого делегата по умолчанию ... Преимущество наличия единого пакета для всего состояния, которое вы хотите передать вместе с событием, довольно очевидно, особенно если в этом состоянии много элементов. Использование объекта вместо строгого типа позволяет передавать событие, возможно, сборкам, которые не имеют ссылки на ваш тип (в этом случае вы можете утверждать, что они не смогут использовать отправителя в любом случае, но это другая история - они все еще могут получить событие).

По своему опыту я согласен со Стивеном Реддом, очень часто отправитель не используется. Единственные случаи, когда мне нужно было идентифицировать отправителя, - это обработчики пользовательского интерфейса со многими элементами управления, использующими один и тот же обработчик событий (чтобы избежать дублирования кода). Однако я отступаю от своей позиции в том, что не вижу проблем в определении строго типизированных делегатов и генерации событий со строго типизированными подписями, в случае, когда я знаю, что обработчик никогда не будет заботиться о том, кто является отправителем (действительно, часто это не следует делать). у меня есть какие-либо возможности в этом типе), и я не хочу, чтобы неудобство было складываться в сумку (подкласс EventArg или универсальный) и распаковывать ее. Если у меня только 1 или 2 элемента в моем состоянии, я в порядке, генерируя эту подпись. Для меня это вопрос удобства: строгая типизация означает, что компилятор держит меня в напряжении, и это уменьшает количество разветвлений, таких как

Foo foo = sender as Foo;
if (foo !=null) { ... }

что делает код лучше:)

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

1 голос
/ 17 сентября 2009

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

1 голос
/ 17 сентября 2009

Вы говорите:

Это приводит к большой обработке событий код как: -

RepeaterItem item = sender as RepeaterItem
if (RepeaterItem != null) { /* Do some stuff */ }

Это действительно лотов кода?

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

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

someControl.Exploded += (s, e) => someControl.RepairWindows();

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

В основном параметр sender - это немного исторического шума, лучше избегать.

Я задавал подобный вопрос здесь.

1 голос
/ 17 сентября 2009

Соглашения существуют только для навязывания согласованности.

Вы МОЖЕТЕ строго набрать свои обработчики событий, если хотите, но спросите себя, даст ли это какое-либо техническое преимущество?

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

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

Если бы эти делегаты были строго типизированы, даже при умном использовании обобщений, было бы ОЧЕНЬ сложно разделить такой обработчик событий. Фактически, строго печатая его, вы навязываете предположение, что обработчики должны заботиться о том, кто является отправителем, когда это не является практической реальностью.

Полагаю, вы должны спросить, почему бы вам строго набирать делегатов для обработки событий? При этом вы бы добавили какие-либо существенные функциональные преимущества? Вы делаете использование более "последовательным"? Или вы просто навязываете предположения и ограничения только ради строгой типизации?

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