声明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<>
模式有几件我不喜欢的事情:
更多代码意味着需要维护更多代码而没有任何明显优势。
因此,我更喜欢event Action<>
但是,只有在Action&lt;&gt;中有太多类型参数时,才需要额外的类。
答案 0 :(得分:81)
根据之前的一些答案,我将把我的答案分解为三个方面。
首先,使用Action<T1, T2, T2... >
与使用派生类EventArgs
的物理限制。有三个:首先,如果您更改参数的数量或类型,则必须更改每个订阅的方法以符合新模式。如果这是第三方程序集将要使用的面向公众的事件,并且事件args有可能发生变化,那么这将是使用从事件args派生的自定义类的一致性(记住,你仍然可以)使用Action<MyCustomClass>
)其次,使用Action<T1, T2, T2... >
将阻止您将反馈BACK传递给调用方法,除非您有一个与Action一起传递的某种对象(例如,具有Handled属性) 。第三,你没有得到命名参数,所以如果你传递3 bool
个int
,两个string
和DateTime
,你有不知道这些价值观是什么意思。 作为旁注,您仍然可以使用“{1}}”来“安全地触发此事件”。
其次,一致性影响。如果你有一个大型系统,你已经在使用,除了你有一个很好的理由之外,按照设计其余部分的方式几乎总是更好。如果您公开面对需要维护的事件,则替换派生类的能力可能很重要。记住这一点。
第三,在现实生活中,我个人发现,我倾向于为需要与之交互的属性更改等事物创建大量的一次性事件(特别是在使用视图模型进行MVVM时彼此交互)或者该事件只有一个参数。大多数情况下,这些事件采用Action<T1, T2, T2... >
或public event Action<[classtype], bool> [PropertyName]Changed;
的形式。在这些情况下,有两个好处。首先,我得到了一个发行类的类型。如果public event Action SomethingHappened;
声明并且是唯一触发事件的类,我会在事件处理程序中获得一个MyClass
的显式实例。其次,对于诸如属性更改事件之类的简单事件,参数的含义是显而易见的,并在事件处理程序的名称中声明,我不必为这些类型的事件创建无数的类。
答案 1 :(得分:66)
主要区别在于,如果您使用Action<>
,您的活动将不会遵循系统中几乎任何其他事件的设计模式,我认为这是一个缺点。
主导设计模式(除了相同的力量之外)的一个优点是,您可以使用新属性扩展EventArgs
对象,而无需更改事件的签名。如果您使用Action<SomeClassWithProperties>
,这仍然是可能的,但在这种情况下我没有真正看到不使用常规方法的观点。
答案 2 :(得分:15)
在大多数情况下,我会说遵循这种模式。我有偏离它,但很少,并且由于特定原因。在这种情况下,我遇到的最大问题是我可能仍然使用Action<SomeObjectType>
,允许我稍后添加额外的属性,并使用偶尔的双向属性(想想{{1订阅者需要设置事件对象上的属性的其他反馈事件)。一旦你开始关注这一行,你也可以使用Handled
代替某些EventHandler<T>
。
答案 3 :(得分:14)
当您的代码位于300,000行项目中时,会出现wordier方法的优势。
使用动作,就像你一样,没有办法告诉我bool,int和Blah是什么。如果你的动作传递了一个定义参数的对象,那么确定。
使用需要EventArgs的EventHandler,如果您要使用针对其目的评论的属性的getter来完成DiagnosticsArgs示例,那么您的应用程序将更容易理解。另外,请在DiagnosticsArgs构造函数中注释或完全命名参数。
答案 4 :(得分:6)
如果您遵循标准事件模式,则可以添加扩展方法以使事件触发检查更安全/更容易。 (即,下面的代码添加了一个名为SafeFire()的扩展方法,它执行空检查,以及(显然)将事件复制到一个单独的变量中,以防止可能影响事件的常见空竞争条件。)
(虽然我是否有两种想法,你是否应该在null对象上使用扩展方法......)
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();
}
}
答案 5 :(得分:6)
我意识到这个问题已经有10多年的历史了,但在我看来,不仅最明显的答案没有得到解决,而且从这个问题中似乎并不能真正清楚地了解问题的根源。盖子。此外,还有其他有关延迟绑定的问题,以及对委托和lambda的含义(稍后会详细介绍)。
首先要解决房间中800磅重的大象/大猩猩的问题,何时选择event
与Action<T>
/ Func<T>
:
event
时
想要更多的具有多个发布/订阅模型
将执行的语句/ lambda /功能(这是主要
马上就能实现差异。作为事件的示例,让我们使用一个小型控制台应用程序连接一组简单的“标准”事件,如下所示:
public delegate void FireEvent(int num);
public delegate void FireNiceEvent(object sender, SomeStandardArgs args);
public class SomeStandardArgs : EventArgs
{
public SomeStandardArgs(string id)
{
ID = id;
}
public string ID { get; set; }
}
class Program
{
public static event FireEvent OnFireEvent;
public static event FireNiceEvent OnFireNiceEvent;
static void Main(string[] args)
{
OnFireEvent += SomeSimpleEvent1;
OnFireEvent += SomeSimpleEvent2;
OnFireNiceEvent += SomeStandardEvent1;
OnFireNiceEvent += SomeStandardEvent2;
Console.WriteLine("Firing events.....");
OnFireEvent?.Invoke(3);
OnFireNiceEvent?.Invoke(null, new SomeStandardArgs("Fred"));
//Console.WriteLine($"{HeightSensorTypes.Keyence_IL030}:{(int)HeightSensorTypes.Keyence_IL030}");
Console.ReadLine();
}
private static void SomeSimpleEvent1(int num)
{
Console.WriteLine($"{nameof(SomeSimpleEvent1)}:{num}");
}
private static void SomeSimpleEvent2(int num)
{
Console.WriteLine($"{nameof(SomeSimpleEvent2)}:{num}");
}
private static void SomeStandardEvent1(object sender, SomeStandardArgs args)
{
Console.WriteLine($"{nameof(SomeStandardEvent1)}:{args.ID}");
}
private static void SomeStandardEvent2(object sender, SomeStandardArgs args)
{
Console.WriteLine($"{nameof(SomeStandardEvent2)}:{args.ID}");
}
}
输出将如下所示:
如果您对Action<int>
或Action<object, SomeStandardArgs>
进行了同样的操作,则只会看到SomeSimpleEvent2
和SomeStandardEvent2
。
那么event
内部发生了什么事?
如果我们扩展FireNiceEvent
,则编译器实际上正在生成以下内容(与该讨论无关的线程同步,我已经省略了一些细节):
private EventHandler<SomeStandardArgs> _OnFireNiceEvent;
public void add_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
{
Delegate.Combine(_OnFireNiceEvent, handler);
}
public void remove_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
{
Delegate.Remove(_OnFireNiceEvent, handler);
}
public event EventHandler<SomeStandardArgs> OnFireNiceEvent
{
add
{
add_OnFireNiceEvent(value)
}
remove
{
remove_OnFireNiceEvent(value)
}
}
编译器会生成一个私有委托变量,该私有委托变量对于生成它的类名称空间是不可见的。该委托用于订阅管理和后期绑定参与,面向公众的界面是我们都熟悉并喜欢的熟悉的+=
和-=
运营商:)
您可以通过将FireNiceEvent
委托的范围更改为protected来自定义添加/删除处理程序的代码。现在,这使开发人员可以向挂钩添加自定义挂钩,例如日志记录或安全挂钩。这确实提供了一些非常强大的功能,这些功能现在允许根据用户角色等对订阅进行自定义访问。您可以使用lambda做到这一点吗? (实际上,您可以通过自定义编译表达式树来进行设置,但这超出了此响应的范围。)
要从此处的一些回应中解决几点:
改变之间的“脆性”确实没有区别
Action<T>
中的args列表并更改类中的属性
源自EventArgs
。要么不仅需要编译
更改,它们都将更改公共界面,并且需要
版本控制。没什么。
关于哪个是行业标准,取决于哪个地方
这正在被使用,为什么。 Action<T>
,这种情况经常在IoC中使用
和DI,而event
通常用于消息路由,例如GUI和
MQ类型框架。请注意,我经常说 ,而不是总是 。
与lambda相比,代表的生存期不同。一个还必须是 意识到捕获...不仅有闭包,而且还有概念 看看猫拖了什么。这确实会影响记忆 足迹/生命周期以及管理泄漏。
还有一件事,我之前提到过……延迟绑定的概念。使用lambda之类的框架时,关于lambda何时变为“活动”状态,您经常会看到这种情况。这与委托的后期绑定非常不同,后者可能发生不止一次(即lambda始终存在,但是绑定根据需要经常发生在需要时),而不是lambda,后者一旦发生就完成了-魔术消失了,方法/属性将始终绑定。注意事项。
答案 6 :(得分:2)
查看Standard .NET event patterns我们找到了
.NET事件委托的标准签名是:
void OnEventRaised(object sender, EventArgs args);
[...]
参数列表包含两个参数:发件人和事件参数。发送方的编译时类型是System.Object,即使您可能知道更多派生类型始终是正确的。按照惯例,使用对象。
下面在同一页面上,我们找到典型事件定义的示例,类似于
public event EventHandler<EventArgs> EventName;
我们定义了
class MyClass
{
public event Action<MyClass, EventArgs> EventName;
}
处理程序可能已经
void OnEventRaised(MyClass sender, EventArgs args);
其中sender
具有正确的(更多派生)类型。