linq 摘自埃里克·利伯特的博客:“不要关闭循环变量”[重复]

64jmpszr  于 2022-12-15  发布在  其他
关注(0)|答案(4)|浏览(68)

此问题在此处已有答案

12年前就关闭了。

可能重复:

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#行为中的一个缺陷?

krcsximq

krcsximq2#

这是一个微妙的范围问题,与闭包和延迟执行的工作方式有关。
如果不使用局部变量,而是直接使用sequence,则结果IEnumarable将绑定到VARIABLE序列而不是sequence的VALUE,并且在执行查询时,VARIABLE序列将包含sequence的LAST VALUE。
如果你像Eric的例子那样声明另一个局部变量,作用域被限制在每个循环迭代中,因此即使执行被延迟,它也会按预期被求值。

iibxawm4

iibxawm43#

此处使用的LINQ查询导致s的值在其最初定义的范围之外可用(即CartesianProduct方法)。这就是所谓的closure。由于延迟执行,在实际计算LINQ查询时(假设它最终被求值),则封闭的方法将已经完成,并且s变量将“超出作用域”,至少根据传统的作用域规则是这样。在该上下文中引用s仍然是“安全”。
闭包在传统的函数式编程语言中非常方便,而且表现良好,因为在传统的函数式编程语言中,事物本质上是不可变的。而C#首先是一种命令式编程语言,变量在默认情况下是可变的,这一事实是导致这种奇怪的解决方案的问题的基础。
通过在循环范围内创建中间变量,可以有效地指示编译器为LINQ查询的每次迭代分配一个单独的非共享变量。否则,每次迭代都将共享该变量的同一示例,该示例的值(显然)也将相同......这可能不是您想要的。

mftmpeh8

mftmpeh84#

那篇博文上的评论之一是:
但是,在CartesianProduct方法的第一个版本中有一个bug:你关闭了循环变量,所以,由于延迟执行,它会产生最后一个序列与自身的笛卡尔乘积。你需要在foreach循环中添加一个临时的局部变量来使它工作(尽管第二个版本工作得很好)。

相关问题