C# 事件 Action<> 与事件 EventHandler<>
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1431359/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
event Action<> vs event EventHandler<>
提问by Boris Lipschitz
Is there any different between declaring event Action<>
and event EventHandler<>
.
声明event Action<>
和event EventHandler<>
.
Assuming it doesn't matter what object actually raised an event.
假设哪个对象实际上引发了事件并不重要。
for example:
例如:
public event Action<bool, int, Blah> DiagnosticsEvent;
vs
对比
public event EventHandler<DiagnosticsArgs> DiagnosticsEvent;
class DiagnosticsArgs : EventArgs
{
public DiagnosticsArgs(bool b, int i, Blah bl)
{...}
...
}
usage would be almost the same in both cases:
在这两种情况下,用法几乎相同:
obj.DiagnosticsEvent += HandleDiagnosticsEvent;
There are several things that I don't like about event EventHandler<>
pattern:
关于event EventHandler<>
模式,我不喜欢以下几点:
- Extra type declaration derived from EventArgs
- Compulsory passing of object source – often no one cares
- 从 EventArgs 派生的额外类型声明
- 对象源的强制传递——通常没人关心
More code means more code to maintain without any clear advantage.
更多的代码意味着更多的代码需要维护,但没有任何明显的优势。
As a result, I prefer event Action<>
结果,我更喜欢 event Action<>
However, only if there are too many type arguments in Action<>, then an extra class would be required.
然而,只有当 Action<> 中有太多类型参数时,才需要一个额外的类。
采纳答案by Fredrik M?rk
The main difference will be that if you use Action<>
your event will not follow the design pattern of virtually any other event in the system, which I would consider a drawback.
主要区别在于,如果您使用Action<>
您的事件将不会遵循系统中几乎任何其他事件的设计模式,我认为这是一个缺点。
One upside with the dominating design pattern (apart from the power of sameness) is that you can extend the EventArgs
object with new properties without altering the signature of the event. This would still be possible if you used Action<SomeClassWithProperties>
, but I don't really see the point with not using the regular approach in that case.
占主导地位的设计模式的一个好处(除了相同的力量)是您可以EventArgs
使用新属性扩展对象,而无需更改事件的签名。如果您使用Action<SomeClassWithProperties>
,这仍然是可能的,但我真的不明白在这种情况下不使用常规方法的意义。
回答by Marc Gravell
On the most part, I'd say follow the pattern. I havedeviated from it, but very rarely, and for specific reasons. In the case in point, the biggest issue I'd have is that I'd probably still use an Action<SomeObjectType>
, allowing me to add extra properties later, and to use the occasional 2-way property (think Handled
, or other feedback-events where the subscriber needs to to seta property on the event object). And once you've started down that line, you might as well use EventHandler<T>
for some T
.
在大多数情况下,我会说遵循模式。我已经偏离了它,但很少,并且出于特定原因。在这种情况下,我遇到的最大问题是我可能仍然使用Action<SomeObjectType>
,允许我稍后添加额外的属性,并偶尔使用 2-way 属性(想想Handled
,或其他反馈事件,其中订阅者需要在事件对象上设置一个属性)。一旦你开始了这条线,你不妨使用EventHandler<T>
一些T
.
回答by Paul Westcott
If you follow the standard event pattern, then you can add an extension method to make the checking of event firing safer/easier. (i.e. the following code adds an extension method called SafeFire() which does the null check, as well as (obviously) copying the event into a separate variable to be safe from the usual null race-condition that can affect events.)
如果您遵循标准事件模式,那么您可以添加一个扩展方法来使事件触发的检查更安全/更容易。(即,以下代码添加了一个名为 SafeFire() 的扩展方法,它执行空检查,以及(显然)将事件复制到一个单独的变量中,以免受通常会影响事件的空竞争条件的影响。)
(Although I am in kind of two minds whether you should be using extension methods on null objects...)
(虽然我有两种想法,您是否应该在空对象上使用扩展方法......)
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();
}
}
回答by Paul Matovich
The advantage of a wordier approach comes when your code is inside a 300,000 line project.
当您的代码位于 300,000 行的项目中时,更冗长的方法的优势就出现了。
Using the action, as you have, there is no way to tell me what bool, int, and Blah are. If your action passed an object that defined the parameters then ok.
使用操作,就像您一样,无法告诉我 bool、int 和 Blah 是什么。如果您的操作传递了一个定义参数的对象,那么就可以了。
Using an EventHandler that wanted an EventArgs and if you would complete your DiagnosticsArgs example with getters for the properties that commented their purpose then you application would be more understandable. Also, please comment or fully name the arguments in the DiagnosticsArgs constructor.
使用需要 EventArgs 的 EventHandler 并且如果您将使用 getter 完成您的 DiagnosticsArgs 示例,以获取注释其用途的属性,那么您的应用程序将更容易理解。另外,请在 DiagnosticsArgs 构造函数中注释或完全命名参数。
回答by Paul Rohde
Based on some of the previous answers, I'm going to break my answer down into three areas.
基于之前的一些答案,我将把我的答案分成三个方面。
First, physical limitations of using Action<T1, T2, T2... >
vs using a derived class of EventArgs
. There are three: First, if you change the number or types of parameters, every method that subscribes to will have to be changed to conform to the new pattern. If this is a public facing event that 3rd party assemblies will be using, and there is any possiblity that the event args would change, this would be a reason to use a custom class derived from event args for consistencies sake (remember, you COULD still use an Action<MyCustomClass>
) Second, using Action<T1, T2, T2... >
will prevent you from passing feedback BACK to the calling method unless you have a some kind of object (with a Handled property for instance) that is passed along with the Action. Third, you don't get named parameters, so if you're passing 3 bool
's an int
, two string
's, and a DateTime
, you have no idea what the meaning of those values are. As a side note, you can still have a "Fire this event safely method while still using Action<T1, T2, T2... >
".
首先,使用Action<T1, T2, T2... >
vs 使用派生类的物理限制EventArgs
。有三种:第一,如果你改变参数的数量或类型,每个订阅的方法都必须改变以符合新的模式。如果这是第 3 方程序集将使用的面向公众的事件,并且事件 args 可能会发生变化,这将是使用从事件 args 派生的自定义类的原因以保持一致性(请记住,您仍然可以use an Action<MyCustomClass>
) 其次, usingAction<T1, T2, T2... >
将阻止您将反馈传递回调用方法,除非您有某种与 Action 一起传递的对象(例如具有 Handled 属性)。第三,你不会得到命名参数,所以如果你传递 3bool
的一个int
,两个string
's 和 a DateTime
,您不知道这些值的含义是什么。作为旁注,您仍然可以使用Action<T1, T2, T2... >
“仍在使用时安全地触发此事件方法”。
Secondly, consistency implications. If you have a large system you're already working with, it's nearly always better to follow the way the rest of the system is designed unless you have an very good reason not too. If you have publicly facing events that need to be maintained, the ability to substitute derived classes can be important. Keep that in mind.
其次,一致性的含义。如果您已经在使用一个大型系统,那么遵循系统其余部分的设计方式几乎总是更好,除非您有充分的理由不这样做。如果您有需要维护的面向公众的事件,那么替换派生类的能力可能很重要。记在脑子里。
Thirdly, real life practice, I personally find that I tend to create a lot of one off events for things like property changes that I need to interact with (Particularly when doing MVVM with view models that interact with each other) or where the event has a single parameter. Most of the time these events take on the form of public event Action<[classtype], bool> [PropertyName]Changed;
or public event Action SomethingHappened;
. In these cases, there are two benefits. First, I get a type for the issuing class. If MyClass
declares and is the only class firing the event, I get an explicit instance of MyClass
to work with in the event handler. Secondly, for simple events such as property change events, the meaning of the parameters is obvious and stated in the name of the event handler and I don't have to create a myriad of classes for these kinds of events.
第三,在现实生活实践中,我个人发现我倾向于为诸如我需要与之交互的属性更改之类的事情创建许多一次性事件(特别是在使用相互交互的视图模型执行 MVVM 时)或事件发生的地方单个参数。大多数情况下,这些事件采用public event Action<[classtype], bool> [PropertyName]Changed;
或的形式public event Action SomethingHappened;
。在这些情况下,有两个好处。首先,我获得了发行类的类型。如果MyClass
声明并且是触发事件的唯一类,我会MyClass
在事件处理程序中获得一个显式实例。其次,对于简单的事件,例如属性更改事件,参数的含义是显而易见的,并且在事件处理程序的名称中进行了说明,我不必为这些类型的事件创建无数的类。
回答by user1832484
Looking at Standard .NET event patternswe find
查看我们发现的标准 .NET 事件模式
The standard signature for a .NET event delegate is:
void OnEventRaised(object sender, EventArgs args);
[...]
The argument list contains two arguments: the sender, and the event arguments. The compile time type of sender is System.Object, even though you likely know a more derivedtype that would always be correct. By convention, use object.
.NET 事件委托的标准签名是:
void OnEventRaised(object sender, EventArgs args);
[...]
参数列表包含两个参数:sender和事件参数。sender 的编译时类型是 System.Object,即使您可能知道一种始终正确的派生类型。按照惯例,使用object。
Below on same page we find an example of the typical event definition which is something like
在同一页面下方,我们找到了一个典型事件定义的示例,类似于
public event EventHandler<EventArgs> EventName;
Had we defined
如果我们定义
class MyClass
{
public event Action<MyClass, EventArgs> EventName;
}
the handler could have been
处理程序可能是
void OnEventRaised(MyClass sender, EventArgs args);
where sender
has the correct (more derived) type.
wheresender
有正确的(更派生的)类型。
回答by Stacy Dudovitz
I realize that this question is over 10 years old, but it appears to me that not only has the most obvious answer not been addressed, but that maybe its not really clear from the question a good understanding of what goes on under the covers. In addition, there are other questions about late binding and what that means with regards to delegates and lambdas (more on that later).
我意识到这个问题已经有 10 多年的历史了,但在我看来,不仅最明显的答案没有得到解决,而且可能从问题中并不能很好地理解幕后发生的事情。此外,还有其他关于后期绑定的问题,以及这对于委托和 lambda 的意义(稍后会详细介绍)。
First to address the 800 lb elephant/gorilla in the room, when to choose event
vs Action<T>
/Func<T>
:
首先解决房间里 800 磅的大象/大猩猩,何时选择event
vs Action<T>
/ Func<T>
:
- Use a lambda to execute one statement or method. Use
event
when you want more of a pub/sub model with multiple statements/lambdas/functions that will execute (this is a majordifference right off the bat). - Use a lambda when you want to compile statements/functions to expression trees. Use delegates/events when you want to participate in more traditional late binding such as used in reflection and COM interop.
- 使用 lambda 来执行一个语句或方法。使用
event
时,您用多条语句/ lambda表达式/功能,将执行(这是一个想多pub / sub模型的主要区别了蝙蝠的权利)。 - 当您想将语句/函数编译为表达式树时,请使用 lambda。当您想要参与更传统的后期绑定(例如在反射和 COM 互操作中使用)时,请使用委托/事件。
As an example of an event, lets wire up a simple and 'standard' set of events using a small console application as follows:
作为事件的示例,让我们使用一个小型控制台应用程序连接一组简单且“标准”的事件,如下所示:
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}");
}
}
The output will look as follows:
输出将如下所示:
If you did the same with Action<int>
or Action<object, SomeStandardArgs>
, you would only see SomeSimpleEvent2
and SomeStandardEvent2
.
如果你对Action<int>
or做同样的事情Action<object, SomeStandardArgs>
,你只会看到SomeSimpleEvent2
and SomeStandardEvent2
。
So whats going on inside of event
?
那么里面发生了event
什么?
If we expand out FireNiceEvent
, the compiler is actually generating the following (I have omitted some details with respect to thread synchronization that isn't relevant to this discussion):
如果我们展开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)
}
}
The compiler generates a private delegate variable which is not visible to the class namespace in which it is generated. That delegate is what is used for subscription management and late binding participation, and the public facing interface is the familiar +=
and -=
operators we have all come to know and love : )
编译器生成一个私有委托变量,该变量对生成它的类命名空间不可见。该委托用于订阅管理和后期绑定参与,面向公众的界面是我们都熟悉和喜爱的熟悉+=
和-=
运营商:)
You can customize the code for the add/remove handlers by changing the scope of the FireNiceEvent
delegate to protected. This now allows developers to add custom hooks to the hooks, such as logging or security hooks. This really makes for some very powerful features that now allows for customized accessibility to subscription based on user roles, etc. Can you do that with lambdas? (Actually you can by custom compiling expression trees, but that's beyond the scope of this response).
您可以通过将FireNiceEvent
委托范围更改为 protected 来自定义添加/删除处理程序的代码。这现在允许开发人员向钩子添加自定义钩子,例如日志记录或安全钩子。这确实产生了一些非常强大的功能,现在允许根据用户角色等定制订阅的可访问性。你能用 lambda 来做到这一点吗?(实际上您可以通过自定义编译表达式树,但这超出了本响应的范围)。
To address a couple of points from some of the responses here:
从这里的一些回复中解决几点:
There really is no difference in the 'brittleness' between changing the args list in
Action<T>
and changing the properties in a class derived fromEventArgs
. Either will not only require a compile change, they will both change a public interface and will require versioning. No difference.With respect to which is an industry standard, that depends on where this is being used and why.
Action<T>
and such is often used in IoC and DI, andevent
is often used in message routing such as GUI and MQ type frameworks. Note that I said often, not always.Delegates have different lifetimes than lambdas. One also has to be aware of capture... not just with closure, but also with the notion of 'look what the cat dragged in'. This does affect memory footprint/lifetime as well as management a.k.a. leaks.
更改 args 列表中的 args 列表
Action<T>
和更改从EventArgs
. 要么不仅需要编译更改,它们都将更改公共接口并需要版本控制。没有不同。关于哪个是行业标准,这取决于它在何处使用以及为什么使用。
Action<T>
此类在 IoC 和 DIevent
中经常使用,并且经常用于消息路由,例如 GUI 和 MQ 类型的框架。请注意,我经常说,并非总是如此。委托与 lambda 的生命周期不同。人们还必须意识到捕获......不仅仅是关闭,而且还有“看看猫拖进来的东西”的概念。这确实会影响内存占用/生命周期以及管理(又名泄漏)。
One more thing, something I referenced earlier... the notion of late binding. You will often see this when using framework like LINQ, regarding when a lambda becomes 'live'. That is very different than late binding of a delegate, which can happen more than once (i.e. the lambda is always there, but binding occurs on demand as often as is needed), as opposed to a lambda, which once it occurs, its done -- the magic is gone, and the method(s)/property(ies) will always bind. Something to keep in mind.
还有一件事,我之前提到过的……延迟绑定的概念。在使用像 LINQ 这样的框架时,您会经常看到这一点,关于 lambda 何时变为“活动”。这与委托的后期绑定非常不同,后者可以发生不止一次(即 lambda 总是在那里,但绑定在需要时按需发生),而不是 lambda,一旦发生,它就完成了-- 魔法消失了,方法/属性将始终绑定。要记住的事情。