代码之家  ›  专栏  ›  技术社区  ›  Iain Galloway

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

  •  69
  • Iain Galloway  · 技术社区  · 15 年前

    根据 Microsoft event naming guidelines , the 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 回复  |  直到 7 年前
        1
  •  39
  •   Jon Skeet    15 年前

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

    我同意这有点奇怪——但为了熟悉,遵守惯例可能是值得的。(即对其他开发人员的熟悉程度)我从来没有特别热衷于 EventArgs 我自己(因为它本身不传递信息),但那是另一个话题。(至少我们有 EventHandler<TEventArgs> 现在-虽然如果还有一个 EventArgs<TContent> 对于只需要传播单个值的常见情况。)

    编辑:它确实使委托更通用,当然-一个委托类型可以在多个事件中重用。我不确定我认为这是一个特别好的理由,特别是在仿制药方面,但我想这是 某物

        2
  •  17
  •   Keith    15 年前

    我认为这次会议有很好的理由。

    让我们以@Erikkallen的例子为例:

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

    这是可能的(并且从.NET 1开始,在泛型之前),因为支持协方差。

    如果你要自上而下,你的问题是完全有意义的——也就是说,你需要在代码中包含事件,所以你可以将它添加到你的控件中。

    然而,约定是让在第一时间编写组件更容易。你知道这是为了 任何 事件基本模式(对象发送器、事件参数e)将起作用。

    当您添加事件时,您不知道它将如何使用,也不想随意地约束使用组件的开发人员。

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

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

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

    我认为这个惯例对于任何外部的或者你希望在一个紧密的NIT团队之外使用的东西都是值得遵循的。

    我喜欢通用事件参数的概念——我已经使用了类似的东西。

        3
  •  9
  •   Chris Shouts    15 年前

    当我希望使用强类型发送者时,使用以下委托。

    /// <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
  •  5
  •   Marc Gravell    15 年前

    仿制药和历史将发挥很大作用,特别是在控制(等)的数量,揭露类似事件。如果没有仿制药,你最终会发现很多事件 Control 这在很大程度上是无用的:

    • 您仍然需要强制执行任何有用的操作(除了引用检查,您也可以使用它 object )
    • 不能在非控件上重复使用事件

    如果我们考虑泛型,那么一切都很好,但是您随后会开始遇到继承问题;如果类 B : A ,则事件应该在 A EventHandler<A, ...> 以及事件 B EventHandler<B, ...> ?再一次,非常混乱,很难使用工具,而且在语言方面有点混乱。

    除非有更好的选择来涵盖所有这些, 对象 工作;事件几乎总是在类实例上,因此没有拳击等-只是一个演员。演员阵容也不是很慢。

        5
  •  4
  •   erikkallen    15 年前

    我想那是因为你应该能做些像

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

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

        6
  •  2
  •   Community CDub    8 年前

    根本没有好的理由,现在有了协变和反变,我认为使用强类型发送器是可以的。请参阅此处的讨论 question

        7
  •  1
  •   Stephen M. Redd    15 年前

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

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

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

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

    如果这些委托是强类型的,即使巧妙地使用了泛型,也很难共享这样的事件处理程序。事实上,通过强烈地输入它,您将假定处理程序应该关心发送者是什么,而这不是实际情况。

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

        8
  •  1
  •   Community CDub    8 年前

    你说:

    这会导致大量的事件处理 代码如下:

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

    真的吗? 太多了 代码?

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

    当您登记参加某个活动时,在这一点上,您必须知道该活动所在的对象,这是您最可能感兴趣的对象:

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

    并且任何特定于事件的东西都应该在EventArgs派生的第二个参数中。

    基本上 发件人 参数是有点历史噪音,最好避免。

    I asked a similar question here.

        9
  •  1
  •   codymanix    15 年前

    这是因为你永远无法确定是谁炒了这件事。无法限制允许哪些类型触发某个事件。

        10
  •  1
  •   Sam Dahan    15 年前

    使用eventhandler(object sender,eventargs e)的模式旨在为所有事件提供识别事件源(sender)的方法,并为所有事件的特定负载提供容器。 此模式的优点还在于,它允许使用相同类型的委托生成许多不同的事件。

    至于此默认委托的参数… 对于您希望与事件一起传递的所有状态,拥有一个包的优势是相当明显的,特别是在该状态中有许多元素的情况下。 使用对象而不是强类型可以将事件传递给没有引用您的类型的程序集(在这种情况下,您可能会认为无论如何它们都无法使用发送者,但这是另一个情况-它们仍然可以获取事件)。

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

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

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

    这么说,这只是我的意见。我经常偏离活动的推荐模式,而且我没有因此而受到任何影响。必须始终清楚 为什么? 偏离它是可以的。 好问题! .

        11
  •  0
  •   Maximilian Mayerl    15 年前

    好吧,这是个好问题。我认为,因为任何其他类型都可以使用您的委托来声明事件,所以您不能确定发送者的类型确实是“MyType”。

        12
  •  0
  •   Eamon Nerbonne    15 年前

    我倾向于为每个事件(或一小群类似事件)使用特定的委托类型。无用的sender和eventargs只是使API混乱,分散了与实际相关的信息。能够跨类“转发”事件,我还没有找到有用的东西——如果您要将类似的事件转发给表示不同类型事件的事件处理程序,那么您只需自己包装事件并提供适当的参数,这一点也不费吹灰之力。此外,转发器往往比最终接收器更了解如何“转换”事件参数。

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