2010-07-06 42 views
2

可能重複:
Why is it bad to use a iteration variable in a lambda expression
C# - The foreach identifier and closures從埃裏克利珀的博客: 「不收,較循環變量」

Eric Lippert's 28 June 2010項:

static IEnumerable<IEnumerable<T>> 
    CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences) 
{ 
    // base case: 
    IEnumerable<IEnumerable<T>> result = new[] { Enumerable.Empty<T>() }; 

    foreach(var sequence in sequences) 
    { 
    var s = sequence; // don't close over the loop variable 

    // recursive case: use SelectMany to build the new product out of the old one 
    result = 
     from seq in result 
     from item in s 
     select seq.Concat(new[] {item}); 
    } 

    return result; 
} 

var s = sequence;看起來像一個沒有操作。爲什麼不是一個?當sequence直接使用時出什麼問題?

而且,更主觀的是:這被認爲是C#行爲中的一個疣?

+4

這已被問及解釋1000次之前http://stackoverflow.com/questions/227820 http://stackoverflow.com/questions/2717377 http://stackoverflow.com/questions/566687 http:// stackoverflow /問題/ 451779 http://stackoverflow.com/questions/230455 http://stackoverflow.com/questions/2951037 http://stackoverflow.com/questions/1688465 http://stackoverflow.com/questions/2242371 – 2010-07-06 22:29:11

+1

呵呵,我不知道這件事,很高興有一百萬條關於這個的話題。 – 2010-07-07 04:03:18

回答

3

這是一個微妙的範圍問題,與閉包和延遲執行工作有關。

如果您不使用局部變量,而是直接進行序列化,結果IEnumarable綁定到VARIABLE序列而不是序列的VALUE,並且在執行查詢時,VARIABLE序列包含最後一個值的序列。

如果您聲明另一個局部變量,如Eric的示例中所示,範圍僅限於每個循環迭代。因此即使執行延期,也會按照預期進行評估。

0

一個對博客文章的評論:

但是,您的第一個問題有一個錯誤 版本的CartesianProduct方法 :您正在關閉循環 變量,因此,由於延遲執行 ,因此它自身產生了最後一個序列的笛卡爾產品 。您需要在foreach循環 中添加一個臨時 局部變量以使其工作(儘管第二個版本 工作正常)。

1

這裏所使用的LINQ查詢導致的s值可用它最初所定義的範圍之外(即,CartesianProduct方法)。這就是所謂的closure。由於延遲執行,到實際評估LINQ查詢(假設它最終被評估)時,封閉方法將已完成,並且s變量將「超出範圍」,至少根據傳統範圍規則。儘管如此,在這種情況下,請參閱s仍然是「安全的」。

在傳統的函數式編程語言中,閉包非常方便且性能良好,其中事物本質上是不可變的。事實上,C#最重要的是命令式編程語言,其中變量默認是可變的,這是導致這種奇怪的解決方法的問題的基礎。

通過在循環範圍內創建中間變量,可以有效地指示編譯器爲LINQ查詢的每次迭代分配一個單獨的非共享變量。否則,每次迭代將共享變量的同一個實例,這也將(顯然)是相同的值...可能不是你想要的。