В обработчике событий C#, почему параметр “отправителя” должен быть объектом?

Согласно рекомендациям по именованию события 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) даже когда возможно использовать более определенный тип, и в реальном программировании его важно следовать конвенции.

Править: приманка для поиска: обработчики событий "Правила RSPEC-3906 должны иметь корректную подпись"

71
задан max630 7 August 2019 в 09:10
поделиться

12 ответов

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

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

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

39
ответ дан 24 November 2019 в 13:07
поделиться

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

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

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

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

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

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

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

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

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

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

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

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

17
ответ дан 24 November 2019 в 13:07
поделиться

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

/// <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;
10
ответ дан 24 November 2019 в 13:07
поделиться

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

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

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

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

5
ответ дан 24 November 2019 в 13:07
поделиться

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

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

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

4
ответ дан 24 November 2019 в 13:07
поделиться

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

3
ответ дан 24 November 2019 в 13:07
поделиться

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

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

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

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

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

Я полагаю, что вы должны спросить, почему вам БУДЕТ строго вводить делегатов обработки событий? Сделаете ли вы так, добавив каких-либо значительных функциональных преимуществ? Вы делаете использование более «последовательным»? Или вы просто навязываете предположения и ограничения только ради строгой типизации?

? Или вы просто навязываете предположения и ограничения только ради строгой типизации?

? Или вы просто навязываете предположения и ограничения только ради строгой типизации?

1
ответ дан 24 November 2019 в 13:07
поделиться

Вы говорите:

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

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

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

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

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

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

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

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

Я задал здесь аналогичный вопрос

1
ответ дан 24 November 2019 в 13:07
поделиться

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

1
ответ дан 24 November 2019 в 13:07
поделиться

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

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

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

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

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

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

1
ответ дан 24 November 2019 в 13:07
поделиться

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

0
ответ дан 24 November 2019 в 13:07
поделиться

Я обычно использую определенный тип делегата для каждого события (или небольшой группы подобных событий). Бесполезные аргументы отправителя и события просто загромождают api и отвлекают от действительно релевантной информации. Возможность «пересылать» события между классами - это не то, что я еще не нашел полезным - и если вы пересылаете такие события в обработчик событий, который представляет другой тип события, тогда вы вынуждены оборачивать событие самостоятельно и предоставить подходящие параметры совсем немного. Кроме того, сервер пересылки имеет тенденцию лучше понимать, как «преобразовать» параметры события, чем конечный получатель.

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

0
ответ дан 24 November 2019 в 13:07
поделиться
Другие вопросы по тегам:

Похожие вопросы: