2009-08-16 81 views
0

该函数返回1210而不是385,为什么?.net匿名方法...奇迹在哪里?

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

为什么不启动Reflector并查看编译器为您生成了什么? – 2009-09-05 07:53:51

回答

7

在你的代码中循环变量i是共同的所有功能,它会留给它的最后的值11,稍后将用于计算的总和。

如果更改循环的变量是不共享的,这样在一个范围内的职能分配...

for(int i = 1;i <= 10;i++) { 
    int n = i; 
    functions.Add(() => n * n); 
    } 

...函数将返回385

5

因为在循环结束时,i的值为11。您已将一堆函数添加到列表中 - 不是值 - 但函数都指向一个int,显然它只能有一个值。在你的陈述开始时,它被宣布了一次。与任何变量一样,它的价值将是您对它做的最后一件事。当这些功能实际运行时,它们都会违背这一个值。

如果声明环内的新变量,一个永远不会改变(永远不会被重新分配),使功能运行时,它仍然有一个不变的值的参考。

2

您正在引用一个变异变量,而不是捕获该值。

其他答案告诉你如何做到这一点。

0

我*你调用函数才刚刚之后我被评估()..
在那一刻,我==虽然11

有趣,因为我希望我没有可用了..:d

0

通过在第一个循环中添加代表列表,您创建了所谓的closure。实际上,局限于循环的范围为的局部变量现在存储为嵌套类(由编译器生成)的成员。这就是为什么当你执行第二个循环时,使用循环变量的闭包版本。因为一旦第一循环完成它只是用来,所有的“我的价值观”现在是11

那么,结果将是:10 *(11 * 11)= 1210

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管S移到一个无形的类。匿名方法驻留在该类上。从那里它可以直接访问i。在CalcSquaresSum方法内部,对i的引用都被翻译为对不可见类的引用。

注意,变量sumOfSquares接收相同的处理。当然,发生这种情况是因为编译器足够聪明,意识到只有i被匿名方法使用。