2010-07-06 47 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查询的每次迭代分配一个单独的非共享变量。否则,每次迭代将共享变量的同一个实例,这也将(显然)是相同的值...可能不是你想要的。