来自Eric Lippert的博客:“不要关闭循环变量”

可能重复:
为什么在lambda表达式中使用迭代变量很糟糕
C# – foreach标识符和闭包

来自Eric Lippert的2010年6月28日报道 :

static IEnumerable<IEnumerable> CartesianProduct(this IEnumerable<IEnumerable> sequences) { // base case: IEnumerable<IEnumerable> result = new[] { Enumerable.Empty() }; 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#的行为中,这在多大程度上被视为疣?

Eric自己的一些相关文章,以及评论中的一些有趣的讨论:

  • 关闭循环变量被认为是有害的
  • 关闭循环变量,第二部分

这是一个微妙的范围问题,与闭包和延迟执行的工作方式有关。

如果不使用局部变量,而是直接序列,则结果IEnumarable绑定到VARIABLE序列而不是序列的VALUE,并且在执行查询时,VARIABLE序列包含序列的最后值。

如果您在Eric的示例中声明另一个局部变量,则范围仅限于每个循环迭代。 因此,即使执行被推迟,也将按预期进行评估。

这里使用的LINQ查询导致s的值在最初定义的范围之外(即CartesianProduct方法)可用。 这就是所谓的闭包 。 由于执行延迟,在实际评估LINQ查询时(假设它最终被评估),封闭方法将完成并且s变量将“超出范围”,至少根据传统的范围规则。 尽管如此,在这种背景下引用s仍然是“安全的”。

闭包非常方便,并且在传统的函数式编程语言中表现良好,其中事物自然是不可变的。 事实上,C#最重要的是一种命令式编程语言,默认情况下变量是可变的,这个问题的基础导致了这种奇怪的解决方法。

通过在循环范围内创建中间变量,您可以有效地指示编译器为LINQ查询的每次迭代分配一个单独的非共享变量。 否则,每次迭代将共享变量的相同实例,这也将(显然)是相同的值…可能不是你想要的。

该博客文章的评论之一:

但是,你的CartesianProduct方法的第一个版本中存在一个错误:你正在关闭循环变量,因此,由于延迟执行,它会使最后一个序列的笛卡尔积与自身相关。 您需要在foreach循环中添加一个临时局部变量以使其工作(但第二个版本工作正常)。