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

根据Microsoft事件命名指南 ,C#事件处理程序中的sender参数始终是对象types,即使可以使用更具体的types“。

这导致了很多事件处理代码,如:

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

为什么大会build议不要使用更具体的types来声明事件处理程序?

 MyType { public event MyEventHander MyEvent; } ... delegate void MyEventHander(MyType sender, MyEventArgs e); 

我错过了一个陷阱?

对于后人来说:我同意普遍的观点,即约定使用对象(即使有可能使用更具体的types也要通过EventArgs传递数据,而在现实世界的编程中,重要的是要遵循公约。

那么,这是一个模式,而不是一个规则。 这意味着一个组件可以从另一个组件转发事件,即使它不是正常types的事件,也可以保留原始的发件人。

我同意这有点奇怪 – 但为了熟悉起见,可能值得坚持这个惯例。 (熟悉其他开发者,也就是说)。我从来没有对自己的EventArgs特别热衷(因为它本身没有传达任何信息),但这是另一个话题。 (至less我们现在已经获得了EventHandler<TEventArgs> – 尽pipe如果还有一个EventArgs<TContent>对于只需要传播一个值的常见情况会有帮助。)

编辑:它确实使委托更通用,当然 – 一个委托types可以重用多个事件。 我不确定我是否把它作为一个特别好的理由来购买 – 特别是在generics的基础上 – 但我想这是一个问题

我认为这个惯例有一个很好的理由。

我们来看一下(并展开)@ erikkallen的例子:

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

这是可能的(并且自从.net 1之前,在generics之前),因为支持协方差。

如果你是自上而下的,你的问题总是有意义的,即你需要在你的代码中使用这个事件,所以你把它添加到你的控制中。

然而,这个约定是在编写组件的时候更容易一些。 你知道, 任何事件的基本模式(对象发件人,EventArgs e)将工作。

当您添加事件时,您不知道如何使用它,而且您也不想任意限制使用组件的开发人员。

你的一个generics强types事件的例子在你的代码中很有意义,但是不适合其他开发者编写的其他组件。 例如,如果他们想要使用你的组件与上面的那些:

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

在这个例子中,额外的types约束正在为远程开发人员创造痛苦。 他们现在必须为您的组件创build一个新的代理。 如果他们正在使用你的组件的负载,他们可能需要为每一个委托。

我认为这个公约值得关注,不pipe是外部的,还是你希望在紧密团队之外使用。

我喜欢通用事件参数的想法 – 我已经使用类似的东西了。

当我更喜欢强types的发件人时,我使用下面的代理。

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

generics和历史将起到很大的作用,特别是控制数量(等),揭露类似的事件。 没有generics,最终会有很多暴露Control的事件,这在很大程度上是无用的:

  • 你仍然必须投入去做任何有用的事情(除了参考检查,你也可以用object来做)
  • 您无法重新使用非控件上的事件

如果我们考虑generics,那么一切都是好的,但是随后开始涉及inheritance的问题。 如果B : AB : A ,那么A上的EventHandler<A, ...>应该是EventHandler<A, ...> ,而B上的EventHandler<B, ...>EventHandler<B, ...> ? 再次,非常混乱,难以加工,在语言方面有点混乱。

直到有一个更好的选项涵盖所有这些, object作品; 事件几乎总是在类实例上,所以没有拳击等 – 只是一个强制转换。 铸造不是很慢。

我想这是因为你应该可以做类似的事情

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

你为什么要在你的代码中进行安全的转换? 如果您知道只使用该函数作为中继器的事件处理程序,则您知道该参数总是正确的types,您可以使用投掷转换,例如(Repeater)发送者而不是(sender as Repeater)。

没有什么好的理由,现在有合作和逆变我认为使用强types的发件人是好的。 参见这个问题的讨论

公约只是为了保持一致性。

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

您应该考虑事件处理程序并不总是需要转换发件人…我在实际中看到的大部分事件处理代码都没有使用sender参数。 它是在那里,如果需要,但往往不是。

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

如果这些代表是强types的,即使巧妙地使用generics,分享这样的事件处理程序也是非常困难的。 事实上,通过强调input它,你就强加了一个假设,即处理者应该关心发送者是什么,当这不是实际的现实时。

我想你应该问的是为什么你强烈键入事件处理代表? 这样做你会增加任何显着的function优势? 你是否使用更“一致”? 还是仅仅为了强打字而强加假设和约束呢?

你说:

这导致了很多事件处理代码,如:

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

这是真的很多代码?

我build议永远不要使用sender参数的事件处理程序。 你已经注意到,它不是静态types的。 这不一定是事件的直接发送者,因为有时事件被转发。 因此,同一事件处理程序可能甚至在每次触发时都不会获得相同的sender对象types。 这是一种不必要的隐式耦合forms。

当你参加一个活动的时候,你必须知道这个活动是什么对象,这就是你最感兴趣的东西:

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

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

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

我在这里问了一个类似的问题。

这是因为你永远无法确定是谁发起了这个事件。 没有办法限制哪些types被允许触发某个事件。

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

至于这个默认委托的参数…有一个单一的袋子,你想要传递的事件相当明显,尤其是如果有很多元素在这个状态。 使用对象而不是强types允许将事件传递给可能不包含types引用的程序集(在这种情况下,您可能会争辩说,无论如何他们将无法使用发件人,但这是另一回事 – 他们仍然可以得到的事件)。

根据我自己的经验,我同意Stephen Redd,很多时候发件人没有使用。 唯一需要识别发件人的情况是UI处理程序,许多控件共享相同的事件处理程序(以避免重复代码)。 然而,我偏离了自己的立场,因为我没有发现定义强types委​​托的问题,并且在强制types签名的情况下生成事件,在这种情况下我知道处理程序永远不会关心发件人是谁(事实上,有任何范围到这种types),我不希望填充状态袋(EventArg的子类或通用),并解压缩它的不便。 如果我只有一个或两个元素在我的状态,我可以生成该签名。 这对我来说是一个方便的事情:强大的input意味着编译器让我停留在脚趾上,并且减less了类似的分支

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

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

这就是说,这只是我的意见。 我经常偏离推荐的事件模式,我没有受到任何损失。 总是清楚为什么偏离它是很重要的。 好问题! 。

那么,这是一个很好的问题。 我认为,因为任何其他types可以使用你的委托来声明一个事件,所以你不能确定发件人的types是真正的“MyType”。

我倾向于为每个事件(或一小组类似事件)使用特定的委托types。 无用的发送方和事件标记简单地将api混乱,并从实际相关的信息中分散注意力。 能够通过类“转发”事件并不是我还没有发现的有用的东西 – 如果你将事件转发到代表不同types事件的事件处理程序,自己提供相应的参数是一点努力。 此外,转发者往往比如何最终接收者更好地了解如何“转换”事件参数。

总之,除非有一些紧迫的互操作的原因,否则转储无用的,混乱的参数。