为什么lambda表达式没有“实习”?

字符串是引用类型,但它们是不可变的。 这允许它们编译器实现; 在出现相同的字符串文字的地方,可以引用相同的对象。

委托也是不可变的引用类型。 (使用+=运算符向多播委托添加方法构成赋值 ;这不是可变性。)而且,就像字符串一样,有一种“文字”方式来表示代码中的委托,使用lambda表达式,例如:

 Func func = () => 5; 

该语句的右侧是一个类型为Func的表达式; 但我没有在任何地方显式调用Func构造函数(也不会发生隐式转换)。 所以我认为这基本上是一个文字 。 我在这里误解了我对“文字”的定义吗?

无论如何,这是我的问题。 如果我有两个变量,比如Func类型,我将两个相同的lambda表达式分配给:

 Func x = () => 5; Func y = () => 5; 

…什么阻止编译器将它们视为相同的Func对象?

我问,因为C#4.0语言规范的第6.5.1节明确指出:

将具有相同(可能为空)的捕获的外部变量实例集的语义相同的匿名函数转换为相同的委托类型是允许(但不是必需的)返回相同的委托实例。 这里使用语义相同的术语来表示在所有情况下,在给定相同参数的情况下,匿名函数的执行将产生相同的效果。

当我读到它时,这让我感到惊讶; 如果明确允许这种行为,我希望它能够实现。 但似乎并非如此。 事实上,这已经让很多开发人员陷入困境,尤其是 当lambda表达式用于成功附加事件处理程序而不能删除它们时。 例如:

 class EventSender { public event EventHandler Event; public void Send() { EventHandler handler = this.Event; if (handler != null) { handler(this, EventArgs.Empty); } } } class Program { static string _message = "Hello, world!"; static void Main() { var sender = new EventSender(); sender.Event += (obj, args) => Console.WriteLine(_message); sender.Send(); // Unless I'm mistaken, this lambda expression is semantically identical // to the one above. However, the handler is not removed, indicating // that a different delegate instance is constructed. sender.Event -= (obj, args) => Console.WriteLine(_message); // This prints "Hello, world!" again. sender.Send(); } } 

有没有理由为什么这个行为 – 语义相同的匿名方法的一个委托实例 – 没有实现?

你错了把它称为文字,IMO。 它只是一个可转换为委托类型的表达式。

现在至于“实习”部分 – 一些lambda表达式缓存,因为对于一个单个lambda表达式,有时可以创建并重用单个实例,但是经常会遇到该行代码。 有些不是这样处理的:它通常取决于lambda表达式是否捕获任何非静态变量(无论是通过“this”还是本地方法)。

以下是此缓存的示例:

 using System; class Program { static void Main() { Action first = GetFirstAction(); first -= GetFirstAction(); Console.WriteLine(first == null); // Prints True Action second = GetSecondAction(); second -= GetSecondAction(); Console.WriteLine(second == null); // Prints False } static Action GetFirstAction() { return () => Console.WriteLine("First"); } static Action GetSecondAction() { int i = 0; return () => Console.WriteLine("Second " + i); } } 

在这种情况下,我们可以看到第一个操作被缓存(或者至少生成了两个相等的委托,实际上Reflector表明它确实缓存在静态字段中)。 第二个动作为GetSecondAction的两次调用创建了两个不等的Action实例,这就是“second”在结尾处非空的原因。

在代码中出现在不同位置但具有相同源代码的实习lambda是另一回事。 我怀疑这样做是非常复杂的(毕竟,相同的源代码在不同的地方可能意味着不同的东西),我当然不想依赖它。 如果它不值得依赖,并且为编译器团队做好了很多工作,我认为这不是他们花时间的最佳方式。

有没有理由为什么这个行为 – 语义相同的匿名方法的一个委托实例 – 没有实现?

是。 因为花费时间进行棘手的优化,几乎没有人受益,因此需要花费时间来设计,实施,测试和维护有益于人们的function。

其他答案带来了好处。 我真的没有任何技术 – 每个function都从-100点开始 。

通常,引用相同String实例的字符串变量与引用包含相同字符序列的不同字符串的两个变量之间没有区别。 代表们也是如此。

假设我有两个委托,分配给两个不同的lambda表达式。 然后我将两个代理订阅到一个事件处理程序,并取消订阅一个。 应该是什么结果?

如果在vb或C#中有一种方法来指定一个匿名方法或不引用Me / this的lambda应该被视为静态方法,产生一个可以在整个生命周期中重用的单个委托将是有用的。应用程序。 但是,没有语法表明,并且编译器决定使用不同的lambda表达式返回相同的实例将是一个潜在的重大变化。

编辑我想规范允许它,即使它可能是一个潜在的重大变化,如果任何代码依赖于实例是不同的。

这是允许的,因为C#团队无法控制它。 它们严重依赖于委托(CLR + BCL)和JIT编译器的优化器的实现细节。 现在CLR和抖动实现已经大量增加,没有理由认为它将会结束。 关于委托的规则,CLI规范非常清晰,不足以确保所有这些不同的团队最终都会有一个实现来保证委托对象的相等性是一致的。 至少是因为这会妨碍未来的创新。 这里有很多优化。

Lambdas无法实现,因为它们使用对象来包含捕获的局部变量。 每次构造委托时,此实例都不同。