【发布时间】:2011-06-16 00:30:29
【问题描述】:
字符串是引用类型,但它们是不可变的。这允许它们被编译器实习;任何地方出现相同的字符串文字,都可能引用相同的对象。
委托也是不可变的引用类型。 (使用+= 运算符向多播委托添加方法构成赋值;这不是可变性。)和字符串一样,有一种“字面”方式在代码中表示委托,使用一个 lambda 表达式,例如:
Func<int> func = () => 5;
该语句的右侧是一个类型为Func<int> 的表达式;但我没有在任何地方显式调用 Func<int> 构造函数(也没有发生隐式转换)。所以我认为这本质上是一个文字。我在这里对“字面”的定义有误吗?
无论如何,这是我的问题。如果我有两个变量,例如 Func<int> 类型,并且我为这两个变量分配了相同的 lambda 表达式:
Func<int> x = () => 5;
Func<int> y = () => 5;
...是什么阻止编译器将它们视为相同的 Func<int> 对象?
我问是因为C# 4.0 language specification 的第 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();
}
}
这种行为——语义相同的匿名方法的委托实例——没有实现有什么原因吗?
【问题讨论】:
-
即使它有效,我仍然认为通过复制 lambda 中的代码来分离事件处理程序是个坏主意。
-
我怀疑这将是另一个“因为预算无法用于设计、实施、测试、记录、发布和维护”的好东西。也许 Eric Lippert 可以提供一些内幕消息。
-
好问题。我假设在代码中看到的 lambda 可能看起来与另一个相同,但由于外部闭包等引用差异,它不会在两个地方编译为相同的 MSIL。编译器必须将其编译为 MSIL 才能发现差异,而不是能够从源代码中“即时”发现字符串文字。由于这是任何 lambda 都需要的额外步骤,并且只能提供很小的尺寸节省和几乎没有的性能提升,因此他们可能只是跳过了它。
-
@LukeH:我同意你的怀疑;我认为让我失望的主要原因是他们在规范中明确指出了这种可能性。也许他们只是认为如果他们有时间可以选择的话会很好。但我觉得如果他们不感兴趣的话,完全省略那部分就好了。
-
@Martinho:我实际上同意你的看法。我只是觉得在规范中看到这一点很奇怪,所以我想我会抛出明显的为什么不呢?。
标签: c# .net lambda delegates expression