2009-08-16 64 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被匿名方法使用。