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