在C#事件处理程序中,为什么“sender”参数必须是一个对象?

时间:2009-09-17 09:24:13

标签: c# events

根据Microsoft event naming guidelines,C#事件处理程序中的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传递数据)的答案中的一般情绪,即使可以使用更具体的类型,在现实世界的编程中, 对于遵循惯例非常重要。

12 个答案:

答案 0 :(得分:39)

嗯,这是一种模式,而不是规则。它确实意味着一个组件可以从另一个组件转发事件,保留原始发件人,即使它不是引发事件的正常类型。

我同意这有点奇怪 - 但是出于熟悉的考虑,它可能值得坚持惯例。 (熟悉其他开发人员,就是这样。)我自己从来没有特别热衷EventArgs(因为它本身没有传达任何信息)但这是另一个话题。 (至少我们现在已经EventHandler<TEventArgs>了 - 虽然如果你只需要传播一个值的常见情况也会有EventArgs<TContent>会有所帮助。)

编辑:它确实使委托更具通用性 - 单个委托类型可以在多个事件中重用。我不确定我是否认为这是一个特别好的理由 - 特别是在泛化的情况下 - 但我想这是某些东西 ......

答案 1 :(得分:17)

我认为这个惯例是有充分理由的。

让我们来看看(并展开)@ 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的想法 - 我已经使用了类似的东西。

答案 2 :(得分:9)

当我更喜欢强类型的发件人时,我使用以下代理。

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

答案 3 :(得分:5)

泛型和历史将发挥重要作用,尤其是暴露类似事件的控件(等)的数量。如果没有泛型,你最终会遇到很多暴露Control的事件,这在很大程度上是无用的:

  • 你仍然需要施展任何有用的东西(除了参考检查,你可以用object做同样的事情)
  • 您无法在非控件上重复使用这些事件

如果我们考虑泛型,那么一切都很好,但是你开始涉及继承问题;如果是B : A课,则A上的事件应为EventHandler<A, ...>B上的事件应为EventHandler<B, ...>吗?同样,非常混乱,难以使用工具,并且在语言方面有点混乱。

在有更好的选项涵盖所有这些选项之前,object有效;事件几乎总是在类实例上,所以没有拳击等 - 只是一个演员。而且铸造不是很慢。

答案 4 :(得分:4)

我想这是因为你应该能够做类似

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

为什么要在代码中进行安全转换?如果你知道你只使用函数作为转发器的事件处理程序,你知道参数总是正确的类型,你可以使用抛出转换,例如(转发器)发送方而不是(发送方作为转发器)。

答案 5 :(得分:2)

没有充分的理由,现在有协方差和逆变我认为使用强类型发件人是好的。请参阅此question

中的讨论

答案 6 :(得分:1)

存在惯例只是为了强加一致性。

如果您愿意,可以强烈输入您的事件处理程序,但问问自己这样做是否会提供任何技术优势?

您应该考虑事件处理程序并不总是需要强制转发...我在实际操作中看到的大多数事件处理代码都没有使用sender参数。如果需要,它就在那里,但通常不是。

我经常看到不同对象上的不同事件将共享一个公共事件处理程序的情况,这是因为该事件处理程序不关心发件人是谁。

如果这些代表是强类型的,即使巧妙地使用泛型,也很难共享这样的事件处理程序。事实上,通过强烈输入它,你强加的假设是处理程序应该关心发送者是什么,当这不是实际的现实时。

我想你应该问的是为什么你强烈地输入事件处理代表?通过这样做你会增加任何重要的功能优势吗?您是否使用更“一致”?或者你只是为了强类型而强加假设和约束?

答案 7 :(得分:1)

你说:

  

这导致了大量的事件处理   代码如: -

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

真的很多的代码吗?

我建议永远不要将sender参数用于事件处理程序。正如您所注意到的那样,它不是静态类型的。它不一定是事件的直接发送者,因为有时会转发事件。因此,每次触发时,相同的事件处理程序甚至可能无法获得相同的sender对象类型。这是一种不必要的隐式耦合形式。

当您参加某个活动时,您必须知道该活动所针对的对象,这就是您最感兴趣的内容:

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

事件的任何其他特定内容都应该在EventArgs派生的第二个参数中。

基本上sender参数有点历史噪音,最好避免。

I asked a similar question here.

答案 8 :(得分:1)

这是因为你永远不能确定是谁解雇了这个事件。没有办法限制允许哪些类型触发某个事件。

答案 9 :(得分:1)

使用EventHandler(对象发送者,EventArgs e)的模式旨在为所有事件提供识别事件源(发送者)的方法,并为所有事件的特定有效负载提供容器。 此模式的优点还在于它允许使用相同类型的委托生成许多​​不同的事件。

至于这个默认委托的参数...... 对于您希望与事件一起传递的所有状态,单个包的优点是相当明显的,特别是如果该状态中有许多元素。 使用对象而不是强类型允许传递事件,可能传递给没有引用您的类型的程序集(在这种情况下,您可能会说他们无论如何都无法使用发件人,但这是另一个故事 - 他们仍然可以参加活动。

根据我自己的经验,我同意Stephen Redd,很多时候不会使用发件人。我需要识别发送者的唯一情况是UI处理程序,许多控件共享相同的事件处理程序(以避免重复代码)。 然而,我离开了他的位置,因为我认为定义强类型的委托并生成具有强类型签名的事件没有问题,在我知道处理程序永远不关心发件人是谁的情况下(实际上,它通常不应该有任何范围到这种类型),我不希望将状态填充到一个包(EventArg子类或通用)和解包它的不便。如果我的状态中只有1个或2个元素,那么我可以生成该签名。 对我来说这是一个方便的问题:强类型意味着编译器让我保持警觉,它减少了分支的类型

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

确实使代码看起来更好:)

这就是说,这只是我的意见。我经常偏离推荐的事件模式,而且我没有遭遇过任何事情。重要的是要始终清楚为什么可以偏离它。 好问题!

答案 10 :(得分:0)

嗯,这是一个很好的问题。我认为因为任何其他类型都可以使用您的委托来声明一个事件,所以您无法确定发件人的类型是否真的是“MyType”。

答案 11 :(得分:0)

我倾向于为每个事件(或一小组类似事件)使用特定的委托类型。无用的发送方和eventargs简单地使api混乱并分散实际相关的信息位。能够跨类“转发”事件并不是我尚未发现有用的东西 - 如果你将这样的事件转发给代表不同类型事件的事件处理程序,那么被迫包装事件你自己并提供适当的参数是很少的努力。此外,转发器往往比最终接收器更好地了解如何“转换”事件参数。

简而言之,除非出现一些紧迫的互操作原因,否则请抛弃无用的,令人困惑的参数。