问题描述
.NET 中的事件有一个标准模式 - 它们使用 delegate
类型,该类型接受一个名为 sender 的普通对象,然后是第二个参数中的实际有效负载",该参数应派生自 <代码>EventArgs.
There's a standard pattern for events in .NET - they use a delegate
type that takes a plain object called sender and then the actual "payload" in a second parameter, which should be derived from EventArgs
.
从 EventArgs
派生第二个参数的基本原理似乎很清楚(请参阅 .NET Framework 标准库注释参考).随着软件的发展,它旨在确保事件接收器和源之间的二进制兼容性.对于每个事件,即使它只有一个参数,我们也会派生一个自定义事件参数类,该类具有包含该参数的单个属性,因此我们保留了在未来版本中向有效负载添加更多属性的能力,而不会破坏现有的客户端代码.在独立开发组件的生态系统中非常重要.
The rationale for the second parameter being derived from EventArgs
seems pretty clear (see the .NET Framework Standard Library Annotated Reference). It is intended to ensure binary compatibility between event sinks and sources as the software evolves. For every event, even if it only has one argument, we derive a custom event arguments class that has a single property containing that argument, so that way we retain the ability to add more properties to the payload in future versions without breaking existing client code. Very important in an ecosystem of independently-developed components.
但我发现零参数也是如此.这意味着如果我的第一个版本中有一个没有参数的事件,我会写:
But I find that the same goes for zero arguments. This means that if I have an event that has no arguments in my first version, and I write:
public event EventHandler Click;
...那我做错了.如果我将来将委托类型更改为新类作为其有效负载:
... then I'm doing it wrong. If I change the delegate type in the future to a new class as its payload:
public class ClickEventArgs : EventArgs { ...
...我将破坏与我的客户端的二进制兼容性.客户端最终绑定到一个内部方法 add_Click
的特定重载,该方法采用 EventHandler
,如果我更改委托类型,那么他们找不到该重载,所以有MissingMethodException
.
... I will break binary compatibility with my clients. The client ends up bound to a specific overload of an internal method add_Click
that takes EventHandler
, and if I change the delegate type then they can't find that overload, so there's a MissingMethodException
.
好的,如果我使用方便的通用版本呢?
Okay, so what if I use the handy generic version?
public EventHandler<EventArgs> Click;
不,仍然错,因为 EventHandler
不是 EventHandler
.
No, still wrong, because an EventHandler<ClickEventArgs>
is not an EventHandler<EventArgs>
.
所以要获得 EventArgs
的好处,您必须从中派生,而不是直接使用它.如果你不这样做,你可能就不会使用它(在我看来).
So to get the benefit of EventArgs
, you have to derive from it, rather than using it directly as is. If you don't, you may as well not be using it (it seems to me).
然后是第一个参数,sender
.在我看来,这似乎是邪恶耦合的秘诀.事件触发本质上是一个函数调用.一般来说,该函数是否应该有能力从堆栈中挖掘并找出调用者是谁,并相应地调整其行为?我们是否应该强制要求接口应该是这样的?
Then there's the first argument, sender
. It seems to me like a recipe for unholy coupling. An event firing is essentially a function call. Should the function, generally speaking, have the ability to dig back through the stack and find out who the caller was, and adjust its behaviour accordingly? Should we mandate that interfaces should look like this?
public interface IFoo
{
void Bar(object caller, int actualArg1, ...);
}
毕竟,Bar
的实现者可能想知道调用者
是谁,所以他们可以查询更多信息!我希望你现在正在呕吐.为什么事件会有所不同?
After all, the implementor of Bar
might want to know who the caller
was, so they can query for additional information! I hope you're puking by now. Why should it be any different for events?
所以即使我准备为我声明的每个事件创建一个独立的 EventArgs
派生类,只是为了让我在使用 EventArgs
时值得无论如何,我肯定更愿意放弃 object sender 参数.
So even if I am prepared to take the pain of making a standalone EventArgs
-derived class for every event I declare, just to make it worth my while using EventArgs
at all, I definitely would prefer to drop the object sender argument.
Visual Studio 的自动完成功能似乎并不关心您为事件使用的委托 - 您可以键入 +=
[hit Space, Return] 并编写一个为您提供的处理程序方法,该方法与它碰巧是什么委托相匹配.
Visual Studio's autocompletion feature doesn't seem to care what delegate you use for an event - you can type +=
[hit Space, Return] and it writes a handler method for you that matches whatever delegate it happens to be.
那么偏离标准模式会失去什么价值?
So what value would I lose by deviating from the standard pattern?
作为一个额外的问题,C#/CLR 4.0 会做些什么来改变这一点,也许是通过代表中的逆变?我试图对此进行调查,但遇到了另一个问题.我最初将问题的这一方面包含在另一个问题中,但它在那里引起了混乱.而且把它分成三个问题似乎有点过分......
As a bonus question, will C#/CLR 4.0 do anything to change this, perhaps via contravariance in delegates? I attempted to investigate this but hit another problem. I originally included this aspect of the question in that other question, but it caused confusion there. And it seems a bit much to split this up into a total of three questions...
更新:
事实证明,我想知道逆变对整个问题的影响是正确的!
Turns out I was right to wonder about the effects of contravariance on this whole issue!
正如其他地方指出的,新的编译器规则在类型系统中留下了一个在运行时爆炸的漏洞.通过将 EventHandler<T>
定义为不同于 Action<T>
来有效地堵住这个漏洞.
As noted elsewhere, the new compiler rules leave a hole in the type system that blows up at runtime. The hole has effectively been plugged by defining EventHandler<T>
differently to Action<T>
.
所以对于事件,为了避免这种类型的漏洞,你不应该使用 Action<T>
.这并不意味着你必须使用 EventHandler<TEventArgs>
;这只是意味着如果您使用通用委托类型,请不要选择启用逆变的类型.
So for events, to avoid that type hole you should not use Action<T>
. That doesn't mean you have to use EventHandler<TEventArgs>
; it just means that if you use a generic delegate type, don't pick one that is enabled for contravariance.
推荐答案
什么都没有,你什么都没有.自从 .NET 3.5 出现以来,我一直在使用 Action<>
,它更加自然且易于编程.
Nothing, you lose nothing. I've been using Action<>
since .NET 3.5 came out and it is far more natural and easier to program against.
我什至不再处理生成的事件处理程序的 EventHandler
类型,只需编写您想要的方法签名并将其与 lambda 连接:
I don't even deal with the EventHandler
type for generated event handlers anymore, simply write the method signature you want and wire it up with a lambda:
btnCompleteOrder.OnClick += (o,e) => _presenter.CompleteOrder();
这篇关于如果放弃 .NET 中的标准 EventHandler 模式,我会失去什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!