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

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

问题描述

字符串是引用类型,但它们是不可变的。这允许他们被编译器 在同一个字符串文字出现的地方,同一个对象可能被引用。



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

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

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



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

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

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



我问,因为 C#4.0语言规范明确说明:



< blockquote>

与相同
(可能为空)的语法相同的
匿名函数的转换相同的
委托类型的捕获的外部
变量实例集合允许(但不需要
)返回相同的代理
实例。术语语义为
相同,这里用来表示
执行匿名函数
在所有情况下都会产生相同参数的
效果。


当我读它时,这让我感到惊讶;如果这个行为被明确的允许,我本来希望能够实现。但似乎不是这样。这实际上让很多开发者陷入困境,尤其是。当lambda表达式被用来成功附加事件处理程序,而无法删除它们。例如:

  class EventSender 
{
public event EventHandler Event;
public void Send()
{
EventHandler handler = this.Event;
if(handler!= null){handler(this,EventArgs.Empty); }
}
}

类程序
{
static string _message =Hello,world!;

static void Main()
{
var sender = new EventSender();
sender.Event + =(obj,args)=> Console.WriteLine(_message);
sender.Send();

//除非我错了,这个lambda表达式在语义上与上面的一样是
//。但是,处理程序不会被删除,表示
//构造了一个不同的委托实例。
sender.Event - =(obj,args)=> Console.WriteLine(_message);

//打印Hello,world!再次。
sender.Send();
}
}

一个用于语义相同的匿名方法的委托实例未被实现?

解决方案

你被误认为是文字,海事组织。这只是一个可转换为委托类型的表达式。



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



这是一个缓存示例:

  using System; 

class程序
{
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 实例,这就是为什么第二不是 - 在结尾处 -



出现在代码中不同位置但具有相同源代码的Interning lambdas是另一回事。我怀疑这样做是非常复杂的(毕竟,相同的源代码可能意味着不同的地方不同的东西),而且我肯定不希望这样发生。如果不值得依靠,编译器团队的工作很多,我不认为这是他们花时间的最佳方式。


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.

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;

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?

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;

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

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.

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();
    }
}

Is there any reason why this behavior—one delegate instance for semantically identical anonymous methods—is not implemented?

解决方案

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

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).

Here's an example of this caching:

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);
    }
}

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.

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表达式不是“interned”?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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