我意识到这个问题已经有10多年的历史了,但在我看来,不仅没有解决最明显的答案,而且从这个问题中似乎并不能真正清楚地了解幕后的情况。此外,还有其他有关延迟绑定的问题,这对于委托人和lambda意味着什么(稍后会详细介绍)。
首先要解决房间中800磅的大象/大猩猩的问题,何时选择eventvs Action<T>/ Func<T>:
- 使用lambda执行一个语句或方法。使用
event时,您用多条语句/ lambda表达式/功能,将执行(这是一个想多pub / sub模型的主要
区别了蝙蝠的权利)。
- 当您要将语句/函数编译为表达式树时,请使用lambda。当您想参与更传统的后期绑定(例如在反射和COM互操作中使用)时,请使用委托/事件。
作为事件的示例,让我们使用一个小型控制台应用程序连接一组简单的“标准”事件,如下所示:
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做到这一点吗?(实际上,您可以通过自定义编译表达式树来进行设置,但这超出了此响应的范围)。
要从此处的一些回应中解决几点:
在更改args列表Action<T>和更改从派生的类中的属性之间的“脆性”确实没有区别EventArgs。要么不仅需要更改编译,还需要更改公共接口并需要版本控制。没有不同。
关于哪个是行业标准,这取决于在哪里使用以及为什么使用。Action<T>这些通常用在IoC和DI中,并且event经常用在诸如GUI和MQ类型框架之类的消息路由中。请注意,我经常但不总是说。
代表的生命周期与lambdas不同。人们还必须意识到捕捉……不仅是封闭,而且还有“看猫所拖的东西”的概念。这确实会影响内存占用/寿命以及管理(也称为泄漏)。
还有一件事,我之前提到过……后期绑定的概念。使用lambda之类的框架时,关于lambda何时变为“活动”状态,您经常会看到这种情况。这与委托的后期绑定非常不同,后者可以多次发生(即,lambda始终存在,但是绑定根据需要经常发生,需要),而不是lambda,后者一旦发生就完成了-魔法消失了,方法/属性将始终绑定。要记住的事情。