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

Dan*_*Tao 15 .net c# lambda delegates expression

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

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

Func<int> func = () => 5;
Run Code Online (Sandbox Code Playgroud)

该语句的右侧是一个类型为的表达式Func<int>; 但我没有明确地调用Func<int>构造函数(也没有发生隐式转换).所以我认为这基本上是一个文字.我在这里误解了我对"文字"的定义吗?

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

Func<int> x = () => 5;
Func<int> y = () => 5;
Run Code Online (Sandbox Code Playgroud)

...什么阻止编译器将这些视为同一个Func<int>对象?

我问,因为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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*eet 11

你错误称它为文字,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们可以看到,第一个动作是缓存(或至少两个相等制作的代表,而实际上反射表明,它确实一个静态字段缓存).第二个动作Action为两个调用创建了两个不相等的实例GetSecondAction,这就是为什么"second"在结尾处非空.

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


Eri*_*ert 6

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

是.因为有利于几乎没有人需要时间,从设计,实施,测试和维护功能远一个棘手的优化花时间造福人民.