通过放弃.NET中的标准EventHandler模​​式我会失去什么?

时间:2009-07-13 16:20:04

标签: c# events delegates binary-compatibility

.NET中的事件有一个标准模式 - 它们使用delegate类型,它接受一个名为sender的普通对象,然后是第二个参数中的实际“payload”,它应该来自{{1} }。

EventArgs派生的第二个参数的基本原理似乎很清楚(参见.NET Framework Standard Library Annotated Reference)。它旨在确保随着软件的发展,事件接收器和源之间的二进制兼容性。对于每个事件,即使它只有一个参数,我们派生一个自定义事件参数类,它具有包含该参数的单个属性,这样我们就可以保留在未来版本中向有效负载添加更多属性而不破坏现有客户端代码的能力。 。在独立开发组件的生态系统中非常重要。

但我发现零参数也是如此。这意味着如果我的第一个版本中有一个没有参数的事件,那么我写道:

EventArgs

...然后我做错了。如果我将来的委托类型更改为新的类作为其有效负载:

public event EventHandler Click;

...我将破坏与客户的二进制兼容性。客户端最终绑定到内部方法public class ClickEventArgs : EventArgs { ... 的特定重载,该方法占用add_Click,如果我更改委托类型,则他们无法找到该重载,因此有一个EventHandler

好的,那么如果我使用方便的通用版本呢?

MissingMethodException

不,仍然是错误的,因为public EventHandler<EventArgs> Click; 不是EventHandler<ClickEventArgs>

为了获得EventHandler<EventArgs>的好处,你从中派生,而不是直接使用它。如果你不这样做,你也可以不使用它(在我看来)。

然后是第一个参数EventArgs。在我看来,这似乎是一个邪恶的耦合配方。事件触发本质上是一个函数调用。一般来说,这个函数是否应该能够通过堆栈挖掘并找出调用者是谁,并相应地调整其行为?我们是否应该强制接口看起来像这样?

sender

毕竟,public interface IFoo { void Bar(object caller, int actualArg1, ...); } 的实现者可能想知道Bar是谁,所以他们可以查询其他信息!我希望你现在正在呕吐。为什么事件会有所不同?

所以,即使我已经准备好为我宣布的每个事件制作一个独立的caller - 派生类的痛苦,只是为了让我在使用EventArgs时值得一试,我绝对会更喜欢删除对象发送者参数。

Visual Studio的自动完成功能似乎并不关心您为事件使用的委托 - 您可以键入EventArgs [命中空格,返回] 并为您编写处理程序方法它匹配恰好是代理的任何代理。

那么偏离标准模式我会失去什么价值?

作为一个奖金问题,C#/ CLR 4.0会做些什么改变这一点,也许是通过代表们的逆转?我尝试对此进行调查,但点击another problem。我最初将问题的这一方面包括在另一个问题中,但它引起了混乱。将这一点分成总共三个问题似乎有点太多了......

更新

事实证明,我对在这整个问题上逆变的影响感到好奇!

作为noted elsewhere,新的编译器规则在类型系统中留下了一个在运行时爆炸的漏洞。通过将+=EventHandler<T>进行不同的定义,有效地堵塞了这个洞。

因此,对于事件,要避免使用Action<T>类型的洞。这并不意味着你必须使用Action<T>;它只是意味着如果您使用通用委托类型,请不要选择一个启用了逆变法的类型。

2 个答案:

答案 0 :(得分:7)

没什么,你什么也没有失去。自从.NET 3.5问世以来,我一直在使用Action<>,而且编程起来更自然,更容易。

我甚至不再处理生成的事件处理程序的EventHandler类型,只需编写所需的方法签名并用lambda连接它:

btnCompleteOrder.OnClick += (o,e) => _presenter.CompleteOrder();

答案 1 :(得分:1)

我也不喜欢事件处理程序模式。在我看来,Sender对象实际上并没有那么有用。如果事件说某些对象发生了某些事情(例如更改通知),那么在EventArgs中获取信息会更有帮助。对于Sender来说,我唯一可以看到的唯一用途就是取消订阅活动,但并不总是清楚应该取消订阅哪个活动。

顺便说一句,如果我有我的druthers,一个Event不会是AddHandler方法和RemoveHandler方法;它只是一个AddHandler方法,它将返回一个可以用于取消订阅的MethodInvoker。我没有Sender参数,而是让第一个参数成为取消订阅所需的MethodInvoker的副本(如果对象发现自己接收了取消订阅者已丢失的事件)。标准MulticastDelegate不适合调度事件(因为每个订阅者应该接收不同的取消订阅委托),但取消订阅事件不需要通过调用列表进行线性搜索。