事件操作<>与事件EventHandler <>

声明event Action<>event EventHandler<>之间是否有任何不同?

假设什么对象实际上引发了事件并不重要。

例如:

 public event Action<bool, int, Blah> DiagnosticsEvent; 

VS

 public event EventHandler<DiagnosticsArgs> DiagnosticsEvent; class DiagnosticsArgs : EventArgs { public DiagnosticsArgs(bool b, int i, Blah bl) {...} ... } 

在这两种情况下使用情况几乎相同:

 obj.DiagnosticsEvent += HandleDiagnosticsEvent; 

event EventHandler<>模式有几个我不喜欢的事情:

  • 额外的从EventArgs派生的types声明
  • 对象源的强制传递 – 通常没有人关心

更多的代码意味着更多的代码维护没有任何明显的优势

因此,我更喜欢event Action<>

但是,只有在Action <>中有太多types参数时,才需要额外的类。

主要区别在于,如果你使用Action<>你的事件将不会遵循系统中事实上任何其他事件的devise模式,我认为这是一个缺点。

与主导devise模式(除了相同的力量之外)的一个好处是,您可以用新的属性扩展EventArgs对象而不改变事件的签名。 如果你使用Action<SomeClassWithProperties> ,这仍然是可能的,但是在这种情况下,我没有真正看到没有使用常规方法的观点。

根据以前的一些答案,我将把我的答案分成三个方面。

首先,使用派生类EventArgs使用Action<T1, T2, T2... > vs的物理限制。 有三个:首先,如果你改变参数的数量或types,每一个订阅的方法都必须改变,以符合新的模式。 如果这是第三方程序集将要使用的面向公众的事件,并且事件参数可能会改变,那么这将是使用从事件参数派生的自定义类来达到一致性的原因(请记住,您仍然可以使用Action<MyCustomClass> )第二,使用Action<T1, T2, T2... >将阻止您将调用方法的反馈传回给调用方法,除非您有某种types的对象(例如Handled属性)与行动一起传递。 第三,你没有得到命名参数,所以如果你传递了3个bool的一个int ,两个string和一个DateTime ,你不知道这些值的含义是什么。 作为一个方面说明,你仍然可以有一个“安全地触发这个事件的方法,同时仍然使用Action<T1, T2, T2... > ”。

其次,一致性影响。 如果你有一个大系统已经在使用,那么按照系统其余部分的devise方法总是比较好,除非你有一个非常好的理由。 如果您公开面对需要维护的事件,则派生类的替代能力可能很重要。 记住这一点。

第三,现实生活实践中,我个人发现,我倾向于为需要与之交互的属性更改(特别是当MVVM与视图模型彼此交互)或事件发生时创build了很多一次性事件一个参数。 大多数情况下,这些事件采取public event Action<[classtype], bool> [PropertyName]Changed;public event Action SomethingHappened; 。 在这些情况下,有两个好处。 首先,我得到一个发行类的types。 如果MyClass声明并且是触发事件的唯一类,那么我会在事件处理函数中获得MyClass的显式实例。 其次,对于诸如属性变化事件之类的简单事件,参数的含义是显而易见的,并且以事件处理程序的名义表示,我不必为这些事件创build大量的类。

在大多数情况下,我会说遵循模式。 我已经偏离了它,但是很less,并且由于特定的原因。 在这种情况下,我所遇到的最大的问题是,我可能仍然使用Action<SomeObjectType> ,允许我稍后添加额外的属性,并使用偶尔的双向属性(请考虑Handled或其他反馈事件,其中用户需要在事件对象上设置属性)。 而一旦你开始了这一行,你也可以使用EventHandler<T>作为T

当你的代码在一个300,000行的项目中时,一个比较好的方法的好处就来了。

使用这个动作,就像你一样,没有办法告诉我bool,int和Blah是什么。 如果你的动作传递了一个定义参数的对象,那么就OK了。

使用一个需要EventArgs的EventHandler,如果你想使用getter来完成你的DiagnosticsArgs的例子,那么你的应用程序就可以被理解了。 另外,请在DiagnosticsArgs构造函数中注释或完全命名参数。

如果你遵循标准的事件模式,那么你可以添加一个扩展方法来使得事件触发的检查更安全/更简单。 (即下面的代码添加了一个名为SafeFire()的扩展方法,它执行空检查,以及(显然)将事件复制到一个单独的variables中,从而可以影响事件的通常的空竞争条件。

(虽然我是两种心态,你是否应该使用空对象的扩展方法…)

 public static class EventFirer { public static void SafeFire<TEventArgs>(this EventHandler<TEventArgs> theEvent, object obj, TEventArgs theEventArgs) where TEventArgs : EventArgs { if (theEvent != null) theEvent(obj, theEventArgs); } } class MyEventArgs : EventArgs { // Blah, blah, blah... } class UseSafeEventFirer { event EventHandler<MyEventArgs> MyEvent; void DemoSafeFire() { MyEvent.SafeFire(this, new MyEventArgs()); } static void Main(string[] args) { var x = new UseSafeEventFirer(); Console.WriteLine("Null:"); x.DemoSafeFire(); Console.WriteLine(); x.MyEvent += delegate { Console.WriteLine("Hello, World!"); }; Console.WriteLine("Not null:"); x.DemoSafeFire(); } } 

看看标准的.NET事件模式,我们发现

.NET事件委托的标准签名是:

void OnEventRaised(object sender, EventArgs args);

[…]

参数列表包含两个参数: 发件人和事件参数。 发件人的编译时间types是System.Object,即使您可能知道一个始终正确的派生types。 按照惯例,使用对象

下面在同一页面上,我们find了一个类似于典型事件定义的例子

 public event EventHandler<EventArgs> EventName; 

我们定义了

 class MyClass { public event Action<MyClass, EventArgs> EventName; } 

处理程序本来可以的

 void OnEventRaised(MyClass sender, EventArgs args); 

sender有正确的( 更多派生 )types。