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

根据 Microsoft 事件命名准则,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传递数据) ,即使有可能使用更具体的类型,在现实世界编程中,遵循惯例 很重要。

编辑: 用于搜索的诱饵: RSPEC-3906规则“事件处理程序应具有正确的签名”

19847 次浏览

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

我承认这有点奇怪——但是为了熟悉起见,坚持约定也许是值得的。(也就是说,对其他开发人员的熟悉程度。)我自己从来没有特别热衷于 EventArgs(考虑到它本身没有传达任何信息) ,但这是另一个话题。(至少我们现在已经得到了 EventHandler<TEventArgs>——尽管如果还有一个 EventArgs<TContent>用于常见的情况,即只需要传播一个值,那将会有所帮助。)

编辑: 当然,它确实使委托更具通用性——一个委托类型可以跨多个事件重用。我不确定我是否认为这是一个特别好的理由——特别是在仿制药方面——但我想这是 什么的..。

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

泛型和历史将发挥重要作用,特别是对于公开类似事件的控件数量(等等)。如果没有泛型,就会出现大量暴露 Control的事件,而这些事件基本上是无用的:

  • 您仍然必须进行强制转换才能执行任何有用的操作(也许除了引用检查之外,您可以使用 object进行同样的检查)
  • 不能在非控件上重用事件

如果我们考虑泛型,那么一切都很好,但是你开始进入继承的问题; 如果类 B : A,那么 A上的事件应该是 EventHandler<A, ...>,而 B上的事件应该是 EventHandler<B, ...>?同样,非常令人困惑,难以加工,而且在语言方面有点混乱。

除非有一个更好的选项涵盖所有这些,否则 object可以工作; 事件几乎总是在类实例上,所以没有装箱等——只有强制转换。选角也不是很慢。

我想那是因为你应该能够

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

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

约定的存在只是为了强加一致性。

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

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

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

如果这些委托是强类型的,即使巧妙地使用了泛型,也很难共享这样的事件处理程序。实际上,通过强类型化它,您可以假设处理程序应该关心发送方是什么,而实际情况并非如此。

我想您应该问的是,为什么要强烈键入事件处理委托?这样做是否会增加任何重要的功能优势?你是不是让用法更“一致”了?还是仅仅为了强类型而强加假设和约束?

你说:

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

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

真的是 很多代码吗?

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

当您加入一个事件时,在这一点上您必须知道该事件所在的对象,而这正是您最可能感兴趣的:

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

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

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

我也问过类似的问题。

那是因为你永远无法确定是谁开的枪。无法限制允许哪些类型触发某个事件。

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

至于这个默认委托的参数..。 对于您希望随事件一起传递的所有状态,使用单个包的优势相当明显,特别是在该状态中有许多元素的情况下。 使用 object 而不是强类型可以传递事件,可能传递给没有类型引用的程序集(在这种情况下,你可能会争辩说它们无论如何都不能使用发送方,但那是另一回事——它们仍然可以得到事件)。

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

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

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

话虽如此,这只是我的个人观点。我经常偏离事件的建议模式,我没有因此受到任何影响。重要的是要始终清楚的 为什么是可以偏离它。 问得好! .

没有好的理由,现在有协变和反变,我认为这是罚款使用强类型发件人。请参阅本 有个问题中的讨论

如果希望使用强类型发件人,则使用下列委托。

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

我倾向于为每个事件(或一小组类似的事件)使用特定的委托类型。无用的发送者和事件目录只是把 api 弄得乱七八糟,分散了对实际相关信息的注意力。能够跨类“转发”事件并不是我觉得有用的东西——如果你正在转发这样的事件,到一个代表不同类型事件的事件处理程序,然后被迫自己包装事件并提供适当的参数是很轻松的。此外,转发程序往往比最终接收程序更清楚如何“转换”事件参数。

简而言之,除非有什么紧迫的互操作原因,否则就转储无用的、令人困惑的参数。

我觉得召开这次会议是有原因的。

让我们以@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;

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

我认为对于任何外部事物或者你希望在一个紧密团队之外使用的事物,约定都是值得遵循的。

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