我研究了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);
}
}
答案 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);
addFive
是second
makeAdder
是first
second
保留ancestor
,即first
的同一个实例因此,如果我们说
makeAdder = null;
然后无法收集first
的实例。无法通过makeAdder
访问该实例,但可以通过addFive
访问该实例。
在提议的codegen中,可以在此方案中收集first
,因为无法通过addFive
访问该实例。
在这个特定场景中你是正确的优化是合法的。然而,由于Ben Voigt在他的回答中描述的原因,它通常一般是合法的。 如果f
内Curry
发生变异,则local.function
必须变异。但local
无法访问second
<的实例strong>直到外部代表被执行。
C#编译器团队可以选择进行您已经确定的优化,但迄今为止微小的节省不值得烦恼。
我们正在考虑让罗斯林按照你所描述的方式进行优化;也就是说,如果外部变量已知不会变异,则会更积极地捕获其值。我不知道这种优化是否适用于罗斯林。
答案 1 :(得分:2)
您已经两次使用lambda运算符,因此您将获得两个匿名委托,并将捕获的变量提升为状态类型。
内部lambda的状态类型通过引用保存外部lambda的状态类型的原因是这是捕获在C#中的工作方式:您捕获变量,而不是它的价值。
其他一些语言(例如C ++ 11 lambdas)具有替代语法来指示捕获按值与按引用捕获。 C#没有,只是通过引用捕获所有内容。支持按值语义并不是一个很好的理由,因为垃圾收集可以避免在没有捕获按值模式的情况下C ++ 11中存在的生命周期问题。
C#编译器是否注意到永远不会写入变量,因此按值进行捕获与通过引用捕获无法区分?可能,但这是编译器中的额外逻辑,对设计和代码的额外评论,额外的测试。所有这些都是为了在内存占用和局部性方面进行小规模,几乎微不足道的改进它显然没有达到成本效益标准,你可以在Eric Lippert的博客文章中找到这些标题。