事件操作<>与事件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。