模糊编译器的lambda表达式转换

时间:2014-03-21 16:58:14

标签: c# performance compiler-construction lambda

我研究了Y Combinator(使用c#5.0)并且对这种方法感到非常惊讶:

public static  Func<T1, Func<T2, TOut>> Curry<T1, T2, TOut> ( this Func<T1, T2, TOut> f)
{
    return a => b => f(a, b);
}

...由编译器翻译成:

public static Func<T1, Func<T2, TOut>> Curry<T1, T2, TOut>(this Func<T1, T2, TOut> f)
        {
            first<T1, T2, TOut> local = new first<T1, T2, TOut>();
            local.function = f;
            return new Func<T1, Func<T2, TOut>>(local.Curry);
        }
    private sealed class first<T1, T2, TOut>
    {
        private sealed class second
        {
            public first<T1, T2, TOut> ancestor;
            public T1 firstParameter;
            public TOut Curry(T2 secondParameter)
            {
                return ancestor.function(firstParameter, secondParameter);
            }
        }
        public Func<T1, T2, TOut> function;
        public Func<T2, TOut> Curry(T1 firstParameter)
        {
            second local = new second();
            local.ancestor = this;
            local.firstParameter = firstParameter;
            return new Func<T2, TOut>(local.Curry);
        }
    }

因此,第二个类是嵌套的,第一个类不可用于垃圾收集,而我们使用引用second.Curry的委托。与此同时,我们在头等舱中所需要的只是功能。也许我们可以将它(委托)复制到第二个类,然后可以收集第一个类?是的,我们也应该做第二类非嵌套但看起来没问题。据我所知,代表们是按照价值&#34;所以我可以建议它很慢,但同时我们复制 firstParameter ?!所以,可能有人可以解释,为什么编译器会做所有这些事情?) 我说的是这样的:

private sealed class first<T1, T2, TOut>
{
        public Func<T1, T2, TOut> function;
        public Func<T2, TOut> Curry(T1 firstParameter)
        {
            second<T1, T2, TOut> local = new second<T1, T2, TOut>();
            local.function = function;
            local.firstParameter = firstParameter;
            return new Func<T2, TOut>(local.Curry);
        }
    }
    public sealed class second<T1, T2, TOut>
    {
        public T1 firstParameter;
        public Func<T1, T2, TOut> function;
        public TOut Curry(T2 secondParameter)
        {
            return function(firstParameter, secondParameter);
        }
    } 

2 个答案:

答案 0 :(得分:5)

这个问题很难理解。让我澄清一下。您的建议是编译器可以生成

public static Func<T1, Func<T2, TOut>> Curry<T1, T2, TOut>(this Func<T1, T2, TOut> f)
{
    first<T1, T2, TOut> local = new first<T1, T2, TOut>();
    local.function = f;
    return new Func<T1, Func<T2, TOut>>(local.Curry);
}
private sealed class first<T1, T2, TOut>
{
    private sealed class second
    {
        //public first<T1, T2, TOut> ancestor;
        public Func<T1, T2, TOut> function;
        public T1 firstParameter;
        public TOut Curry(T2 secondParameter)
        {
            return /*ancestor.*/function(firstParameter, secondParameter);
        }
    }
    // public Func<T1, T2, TOut> function;
    public Func<T2, TOut> Curry(T1 firstParameter)
    {
        second local = new second();
        // local.ancestor = this;
        local.function = function;
        local.firstParameter = firstParameter;
        return new Func<T2, TOut>(local.Curry);
    }
}

您的主张是因为这种情况,这是一项改进。

Func<int, int, int> adder = (x, y)=>x+y;
Func<int, Func<int, int>> makeAdder = adder.Curry();
Func<int, int> addFive = makeAdder(5);
  • addFivesecond
  • 实例上方法的委托
  • makeAdderfirst
  • 实例上方法的委托
  • 在原始codegen中,second保留ancestor,即first的同一个实例

因此,如果我们说

makeAdder = null;

然后无法收集first的实例。无法通过makeAdder访问该实例,但可以通过addFive访问该实例。

在提议的codegen中,可以在此方案中收集first,因为无法通过addFive访问该实例。

在这个特定场景中你是正确的优化是合法的。然而,由于Ben Voigt在他的回答中描述的原因,它通常一般是合法的。 如果fCurry发生变异,则local.function必须变异。local无法访问second <的实例strong>直到外部代表被执行。

C#编译器团队可以选择进行您已经确定的优化,但迄今为止微小的节省不值得烦恼。

我们正在考虑让罗斯林按照你所描述的方式进行优化;也就是说,如果外部变量已知不会变异,则会更积极地捕获其值。我不知道这种优化是否适用于罗斯林。

答案 1 :(得分:2)

您已经两次使用lambda运算符,因此您将获得两个匿名委托,并将捕获的变量提升为状态类型。

内部lambda的状态类型通过引用保存外部lambda的状态类型的原因是这是捕获在C#中的工作方式:您捕获变量,而不是它的价值。

其他一些语言(例如C ++ 11 lambdas)具有替代语法来指示捕获按值与按引用捕获。 C#没有,只是通过引用捕获所有内容。支持按值语义并不是一个很好的理由,因为垃圾收集可以避免在没有捕获按值模式的情况下C ++ 11中存在的生命周期问题。


C#编译器是否注意到永远不会写入变量,因此按值进行捕获与通过引用捕获无法区分?可能,但这是编译器中的额外逻辑,对设计和代码的额外评论,额外的测试。所有这些都是为了在内存占用和局部性方面进行小规模,几乎微不足道的改进它显然没有达到成本效益标准,你可以在Eric Lippert的博客文章中找到这些标题。