为什么 lambda 表达式不是“实习生"? [英] Why are lambda expressions not "interned"?

查看:21
本文介绍了为什么 lambda 表达式不是“实习生"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Strings are reference types, but they are immutable. This allows for them to be interned by the compiler; everywhere the same string literal appears, the same object may be referenced.

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

Delegates are also immutable reference types. (Adding a method to a multicast delegate using the += operator constitutes assignment; that's not mutability.) And, like, strings, there is a "literal" way to represent a delegate in code, using a lambda expression, e.g.:

Func<int> func = () => 5;

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

The right-hand side of that statement is an expression whose type is Func<int>; but nowhere am I explicitly invoking the Func<int> constructor (nor is an implicit conversion happening). So I view this as essentially a literal. Am I mistaken about my definition of "literal" here?

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

Regardless, here's my question. If I have two variables for, say, the Func<int> type, and I assign identical lambda expressions to both:

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

...是什么阻止编译器将它们视为相同的 Func<int> 对象?

...what's preventing the compiler from treating these as the same Func<int> object?

我问是因为 C# 4.0 语言规范明确指出:

I ask because section 6.5.1 of the C# 4.0 language specification clearly states:

语义相同的转换具有相同功能的匿名函数(可能为空)一组捕获的外部变量实例相同委托类型是允许的(但不是必需)返回相同的委托实例.语义上的术语这里使用相同的意思是匿名函数的执行在所有情况下,都会产生相同的给定相同论点的效果.

Conversions of semantically identical anonymous functions with the same (possibly empty) set of captured outer variable instances to the same delegate types are permitted (but not required) to return the same delegate instance. The term semantically identical is used here to mean that execution of the anonymous functions will, in all cases, produce the same effects given the same arguments.

当我读到它时,这让我很惊讶;如果这种行为被明确允许,我会期望它被实现.但它似乎不是.这实际上给很多开发人员带来了麻烦,尤其是.当 lambda 表达式已用于成功附加事件处理程序而无法删除它们时.例如:

This surprised me when I read it; if this behavior is explicitly allowed, I would have expected for it to be implemented. But it appears not to be. This has in fact gotten a lot of developers into trouble, esp. when lambda expressions have been used to attach event handlers successfully without being able to remove them. For example:

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.它只是一个可转换为委托类型的表达式.

You're mistaken to call it a literal, IMO. It's just an expression which is convertible to a delegate type.

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

Now as for the "interning" part - some lambda expressions are cached , in that for one single lambda expression, sometimes a single instance can be created and reused however often that line of code is encountered. Some are not treated that way: it usually depends on whether the lambda expression captures any non-static variables (whether that's via "this" or local to the method).

这是一个缓存的例子:

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"最后不为 null 的原因.

In this case we can see that the first action was cached (or at least, two equal delegates were produced, and in fact Reflector shows that it really is cached in a static field). The second action created two unequal instances of Action for the two calls to GetSecondAction, which is why "second" is non-null at the end.

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

Interning lambdas which appear in different places in the code but with the same source code is a different matter. I suspect it would be quite complex to do this properly (after all, the same source code can mean different things in different places) and I would certainly not want to rely on it taking place. If it's not going to be worth relying on, and it's a lot of work to get right for the compiler team, I don't think it's the best way they could be spending their time.

这篇关于为什么 lambda 表达式不是“实习生"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆