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

Iai*_*way 71 c# events

根据Microsoft事件命名准则,senderC#事件处理程序中的参数" 始终是类型对象,即使可以使用更具体的类型".

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

RepeaterItem item = sender as RepeaterItem;
if (item != null) { /* Do some stuff */ }
Run Code Online (Sandbox Code Playgroud)

为什么约定建议不要使用更具体的类型声明事件处理程序?

MyType
{
    public event MyEventHander MyEvent;
}

...

delegate void MyEventHander(MyType sender, MyEventArgs e);
Run Code Online (Sandbox Code Playgroud)

我错过了一个陷阱吗?

对于后人:我同意的答案一般情绪的惯例使用对象(并通过传递数据EventArgs),即使它是可以使用更具体的类型,而在现实世界编程它按照重要大会.

Jon*_*eet 39

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

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

编辑:它确实使委托更具通用性 - 单个委托类型可以在多个事件中重用.我不确定我是否认为这是一个特别好的理由 - 特别是考虑到仿制药 - 但我想这 ......

  • 虽然我认为你几乎肯定是正确的,但我仍然不确定我是否看到了优势.事件处理程序本身仍然必须知道它被传递的类型(因为它必须转换为它).我无法理解为什么强烈地输入它会是一件坏事 - 除了你对熟悉现有模式的看法...... (13认同)
  • **以上答案是_very_ old!Times _are_ changing!**:)`"通用委托在根据典型的设计模式定义事件时特别有用,因为sender参数可以强类型化,不再需要与Object进行强制转换."来自[MSDN] (http://msdn.microsoft.com/en-us/library/sx2bwtw7.aspx) (10认同)
  • @Iain:是的,我也没有看到以这种方式定义模式的优点.多年前这可能只是一个糟糕的决定,现在为时已晚,无法明智地改变. (8认同)
  • 嗯,它是从 .NET 1.0 开始实现的,所以它可能是一种向后兼容的代码味道(如果有的话)。 (2认同)
  • @AnorZaken:我不确定你为什么认为我不喜欢这个改变 - 我只是说它在BCL中还没有真正实现,而且值得理解旧模式的原因. (2认同)

Kei*_*ith 17

我认为这个会议有充分的理由.

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

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

这是可能的(并且自从.Net 1以来,在泛型之前)因为支持协方差.

如果您是自上而下的话,那么您的问题就很有意义 - 即您需要在代码中使用该事件,因此您将其添加到您的控件中.

但是,惯例是在首先编写组件时更容易.您知道,对于任何事件,基本模式(对象发送者,EventArgs e)都可以工作.

当您添加事件时,您不知道它将如何使用,并且您不希望任意约束使用您的组件的开发人员.

您的通用强类型事件示例在您的代码中很有意义,但不适合其他开发人员编写的其他组件.例如,如果他们想要将您的组件与上述组件一起使用:

//this won't work
GallowayClass.Changed += SomethingChanged;
Run Code Online (Sandbox Code Playgroud)

在此示例中,附加的类型约束只会给远程开发人员带来痛苦.他们现在必须为您的组件创建一个新的委托.如果他们正在使用您的组件的负载,他们可能需要每个组件的委托.

我认为这个约定值得关注任何外部的,或者你希望在一个紧密的团队之外使用.

我喜欢通用事件args的想法 - 我已经使用了类似的东西.


Chr*_*uts 10

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

/// <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;
Run Code Online (Sandbox Code Playgroud)

这可以通过以下方式使用:

public event EventHandler<TypeOfSender, TypeOfEventArguments> CustomEvent;
Run Code Online (Sandbox Code Playgroud)


Mar*_*ell 5

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

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

如果我们考虑泛型,那么一切都很好,但是你开始涉及继承问题; 如果类B : A,则应该在事件AEventHandler<A, ...>,和事件BEventHandler<B, ...>?同样,非常令人困惑,难以使用工具,并且在语言方面有点混乱.

直到有更好的选择涵盖所有这些,object工作; 事件几乎总是在类实例上,所以没有拳击等 - 只是一个演员.而且铸造不是很慢.