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

时间:2010-07-06 22:05:33

标签: c# linq closures

  

可能重复:
  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 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:3)

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

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

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

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:0)

该博客文章的评论之一:

  

然而,你的第一个错误   CartesianProduct方法的版本   :你正在关闭循环   变量,因此,由于延期   执行,它使笛卡儿   最后序列的产物   本身。你需要添加一个临时的   foreach循环内的局部变量   使它工作(第二个版本   但工作得很好。