这个函数返回1210而不是385,为什么?
public int CalcSquaresSum() {
int sumOfSquares = 0;
List<Func<int>> functions = new List<Func<int>>();
for (int i = 1; i <= 10; i++) {
functions.Add(() => i * i);
}
foreach (var function in functions) {
sumOfSquares += function(); // why function() is always 121
}
return sumOfSquares;
}
答案 0 :(得分:7)
在你的代码中,循环变量i对所有函数都是通用的,它将留给它的最后一个值11,稍后将用它来计算总和。
如果更改循环以将变量分配给未共享的范围中的函数,就像这样......
for(int i = 1;i <= 10;i++) {
int n = i;
functions.Add(() => n * n);
}
...该函数将返回385。
答案 1 :(得分:5)
因为i
的值在循环结束时为11。你已经在列表中添加了一堆函数 - 而不是值 - 但是函数都指向一个int,显然只能有一个值。它在你的陈述开始时被宣布过一次。与任何变量一样,它的值将是你对它做的最后一件事。当这些函数实际运行时,它们都会针对该值运行。
如果在循环中声明一个新变量,那个变量永远不会改变(永远不会被重新赋值),所以当函数运行时,它仍然会引用一个未触及的值。
答案 2 :(得分:2)
您正在引用变异变量,而不是捕获值。
其他答案显示了如何执行此操作。
答案 3 :(得分:0)
我只是在你调用function()之后才进行评估
那一刻我== 11
很有趣,因为我希望我不再可用..:D
答案 4 :(得分:0)
通过在第一个循环中添加委托列表,您创建了所谓的closure。实际上,范围显然限制在循环中的局部变量现在存储为嵌套类的成员(由编译器生成)。这就是为什么当你执行第二个循环时,使用循环变量的闭包版本。因为它只在第一个循环完成后才使用,所以所有“i的值”现在都是11。
因此,结果将是:10 *(11 * 11)= 1210。
答案 5 :(得分:0)
为了好奇,这是编译器在编写代码时实际生成的内容:
[System.Runtime.CompilerServices.CompilerGenerated]
private sealed class AnomClass
{
public int i;
public int CalcSquaresSum_AnonFunc() {
return (this.i * this.i);
}
}
public int CalcSquaresSum()
{
int sumOfSquares = 0;
List<Func<int>> functions = new List<Func<int>>();
AnomClass anonObj = new AnomClass();
Func<int> anonFunc = null;
for (anonObj.i = 1; anonObj.i <= 10; anonObj.i++) {
if (anonFunc == null)
anonFunc = new Func<int>(anonObj.CalcSquaresSum_AnonFunc);
functions.Add(anonFunc);
}
foreach (Func<int> function in functions) {
sumOfSquares += function();
}
return sumOfSquares;
}
正如你所看到的,没有任何魔法涉及。匿名方法只能访问在其作用域之外声明的变量,因为它实际上并未在其作用域之外声明。
真正发生的是变量i
移动到一个不可见的类。匿名方法驻留在该类上。从那里它可以直接访问i
。在CalcSquaresSum
方法中,对i
的引用都被翻译为对不可见类的引用。
请注意,变量sumOfSquares
接受相同的处理。当然,这是因为编译器足够聪明,意识到匿名方法只使用了i
。