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

В обработчике событий С# почему параметр "отправитель" должен быть объектом?

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

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

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 правило "Обработчики событий должны иметь правильную подпись"

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Возьмем (и разворачиваем) пример @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;

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

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

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

Ответ 3

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

/// <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;

Ответ 4

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

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

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

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

Ответ 5

Я предполагаю, что, поскольку вы должны иметь возможность сделать что-то вроде

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

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

Ответ 6

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

Ответ 7

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

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

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

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

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

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

Ответ 8

Вы говорите:

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

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

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

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

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

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

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

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

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

Ответ 9

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

Ответ 10

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

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

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

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

что делает код лучше выглядеть:)

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

Ответ 11

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

Ответ 12

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

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