Dan*_*ker 20 c# events delegates binary-compatibility
.NET中的事件有一个标准模式 - 它们使用一个delegate类型,它接受一个名为sender的普通对象,然后是第二个参数中的实际"payload",它应该从中派生出来EventArgs.
派生的第二个参数的基本原理EventArgs似乎非常清楚(请参阅.NET Framework标准库带注释的参考).它旨在确保随着软件的发展,事件接收器和源之间的二进制兼容性.对于每个事件,即使它只有一个参数,我们派生一个自定义事件参数类,它具有包含该参数的单个属性,这样我们就可以保留在未来版本中向有效负载添加更多属性而不破坏现有客户端代码的能力. .在独立开发组件的生态系统中非常重要.
但我发现零参数也是如此.这意味着如果我的第一个版本中有一个没有参数的事件,我会写:
public event EventHandler Click;
Run Code Online (Sandbox Code Playgroud)
......然后我做错了.如果我将来的委托类型更改为新的类作为其有效负载:
public class ClickEventArgs : EventArgs { ...
Run Code Online (Sandbox Code Playgroud)
...我将破坏与客户的二进制兼容性.客户端上界到内部方法的具体超载add_Click是需要EventHandler的,如果我改变了委托类型,然后他们无法找到超载,所以有一个MissingMethodException.
好的,那么如果我使用方便的通用版本怎么办?
public EventHandler<EventArgs> Click;
Run Code Online (Sandbox Code Playgroud)
不,仍然是错的,因为一个EventHandler<ClickEventArgs>不是EventHandler<EventArgs>.
因此,为了获得好处EventArgs,您必须从中获取,而不是直接使用它.如果你不这样做,你也可以不使用它(在我看来).
然后是第一个论点,sender.在我看来,这似乎是一个邪恶的耦合配方.事件触发本质上是一个函数调用.一般来说,这个函数是否应该能够通过堆栈挖掘并找出调用者是谁,并相应地调整其行为?我们应该强制要求接口看起来像这样吗?
public interface IFoo
{
void Bar(object caller, int actualArg1, ...);
}
Run Code Online (Sandbox Code Playgroud)
毕竟,实现者Bar可能想知道是谁caller,所以他们可以查询更多信息!我希望你现在正在呕吐.为什么事件会有所不同?
因此,即使我已经准备好为EventArgs我声明的每个事件都要为一个独立的派生类而烦恼,只是为了让它值得我在使用EventArgs时,我绝对宁愿删除对象发送者参数.
Visual Studio的自动完成功能似乎并不关心您用于事件的委托 - 您可以键入+= [命中空间,返回]并为您编写一个匹配任何委托的处理程序方法.
那么偏离标准模式我会失去什么价值?
作为一个奖金问题,C#/ CLR 4.0会做些什么改变这一点,也许是通过代表们的逆转?我试图调查这个,但遇到了另一个问题.我最初将问题的这一方面包括在另一个问题中,但它引起了混乱.将这一点分成总共三个问题似乎有点太多了......
更新:
事实证明我对这个问题的逆变性的影响感到好奇!
如其他地方所述,新的编译器规则在类型系统中留下了一个在运行时爆炸的漏洞.通过EventHandler<T>不同的定义来有效地插入孔Action<T>.
所以对于事件,要避免使用那种类型的洞Action<T>.这并不意味着你必须使用EventHandler<TEventArgs>; 它只是意味着如果您使用通用委托类型,请不要选择一个启用了逆变的代理类型.
没什么,你什么也没有失去.Action<>自.NET 3.5问世以来,我一直在使用它,它更自然,更容易编程.
我甚至不再处理EventHandler生成的事件处理程序的类型,只需编写您想要的方法签名并使用lambda连接它:
btnCompleteOrder.OnClick += (o,e) => _presenter.CompleteOrder();
Run Code Online (Sandbox Code Playgroud)