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

Evg*_*kov 3 c# compiler-construction performance 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);
}
Run Code Online (Sandbox Code Playgroud)

...由编译器翻译成:

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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

因此,当我们使用引用second.Curry的委托时,第二个类是嵌套的并且第一个类不可用于垃圾收集.与此同时,我们在头等舱中所需要的只是功能.也许我们可以将它(委托)复制到第二个类,然后可以收集第一个类?是的,我们也应该做第二类非嵌套但看起来没问题.据我所知代表们拷入"按价值"这样我就可以认为它是相当缓慢的,但在同一时间,我们复制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);
        }
    } 
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

是?

您的主张是,由于这种情况,这是一项改进.

Func<int, int, int> adder = (x, y)=>x+y;
Func<int, Func<int, int>> makeAdder = adder.Curry();
Func<int, int> addFive = makeAdder(5);
Run Code Online (Sandbox Code Playgroud)
  • addFive 是一个实例的方法的委托 second
  • makeAdder 是一个实例的方法的委托 first
  • 在原始的codegen中,second持有ancestor,这是同一个实例first

因此,如果我们说

makeAdder = null;
Run Code Online (Sandbox Code Playgroud)

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

在建议的codegen中,first可以在此场景中收集,因为实例无法通过addFive.

你是正确的,在这种特殊情况下,优化是合法的.然而,由于Ben Voigt在他的回答中描述的原因,它一般不合法.如果f在内部发生变异Currylocal.function必须进行变异.但是localsecond 执行外部委托之前无权访问实例.

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

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