.NET 中的事件有一个标准模式——它们使用一种delegate
类型,该类型采用一个名为 sender 的普通对象,然后是第二个参数中的实际“有效负载”,该参数应从EventArgs
.
派生第二个参数的理由EventArgs
似乎很清楚(请参阅.NET Framework Standard Library Annotated Reference)。随着软件的发展,它旨在确保事件接收器和源之间的二进制兼容性。对于每个事件,即使它只有一个参数,我们也会派生一个自定义事件参数类,该类具有包含该参数的单个属性,因此我们保留了在未来版本中向有效负载添加更多属性而不破坏现有客户端代码的能力. 在独立开发组件的生态系统中非常重要。
但我发现零参数也是如此。这意味着,如果我的第一个版本中有一个没有参数的事件,我会写:
public event EventHandler Click;
...那我做错了。如果我将来将委托类型更改为新类作为其有效负载:
public class ClickEventArgs : EventArgs { ...
...我将打破与我的客户的二进制兼容性。客户端最终绑定到一个内部方法的特定重载add_Click
,EventHandler
如果我更改委托类型,那么他们就找不到该重载,所以有一个MissingMethodException
.
好的,那么如果我使用方便的通用版本呢?
public EventHandler<EventArgs> Click;
不,仍然是错误的,因为 anEventHandler<ClickEventArgs>
不是EventHandler<EventArgs>
.
因此,要获得 的好处EventArgs
,您必须从中派生,而不是按原样直接使用它。如果你不这样做,你也可能不使用它(在我看来)。
然后是第一个论点,sender
。在我看来,这似乎是邪恶耦合的秘诀。事件触发本质上是一个函数调用。一般来说,该函数是否应该有能力从堆栈中挖掘并找出调用者是谁,并相应地调整其行为?我们是否应该强制要求接口看起来像这样?
public interface IFoo
{
void Bar(object caller, int actualArg1, ...);
}
毕竟,实现者Bar
可能想知道是谁caller
,所以他们可以查询更多信息!我希望你现在正在呕吐。为什么事件应该有所不同?
因此,即使我准备为EventArgs
我声明的每个事件创建一个独立的派生类,只是为了让它值得我使用EventArgs
,我肯定更愿意放弃 object sender 参数。
Visual Studio 的自动完成功能似乎并不关心您为事件使用的委托 - 您可以键入+=
[hit Space, Return]并为您编写一个与它碰巧匹配的处理程序方法。
那么偏离标准模式会失去什么价值呢?
作为一个额外的问题,C#/CLR 4.0 会做些什么来改变这一点,也许是通过代表的逆变?我试图对此进行调查,但遇到了另一个问题。我最初将问题的这一方面包含在另一个问题中,但它在那里引起了混乱。把它分成三个问题似乎有点多……
更新:
事实证明,我想知道逆变对整个问题的影响是正确的!
正如其他地方所指出的,新的编译器规则在类型系统中留下了一个在运行时爆炸的漏洞。EventHandler<T>
通过对 的不同定义,该孔已被有效地堵塞Action<T>
。
所以对于事件,为了避免你不应该使用的那种类型的洞Action<T>
。这并不意味着您必须使用EventHandler<TEventArgs>
; 这只是意味着如果您使用通用委托类型,请不要选择启用逆变的类型。