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

发布于 2024-09-08 18:03:00 字数 1211 浏览 4 评论 0原文

可能的重复:
为什么不好用lambda 表达式中的迭代变量
C# - foreach 标识符和闭包

来自 Eric Lippert 2010 年 6 月 28 日 条目:

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

Possible Duplicates:
Why is it bad to use a iteration variable in a lambda expression
C# - The foreach identifier and closures

From Eric Lippert's 28 June 2010 entry:

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;
}

The var s = sequence; looks like a no-op. Why isn't it one? What goes wrong when sequence is used directly?

And, more subjectively: to what extent is this considered a wart in C#'s behaviour?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

胡渣熟男 2024-09-15 18:03:00

埃里克本人的几篇相关文章,以及评论中的一些有趣的讨论:

A couple of relevant articles from Eric himself, along with some interesting discussion in the comments:

难得心□动 2024-09-15 18:03:00

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

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

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

It's a subtle scope issue that has to do with how closures and deferred execution work.

If you don't use a local variable, but instead sequence directly, the result IEnumarable is bound to the VARIABLE sequence rather than the VALUE of sequence and at the time the query is executed, the VARIABLE sequence contains the LAST VALUE of sequences.

If you declare another local variable as in Eric's example, the scope is limited to each loop iteration. Therefore it will be evaluated as intended, even if execution is deferred.

人心善变 2024-09-15 18:03:00

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

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

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

The LINQ query being used here causes the value of s to be available outside the scope of where it was originally defined (that is, the CartesianProduct method). This is what is referred to as a closure. Due to delayed execution, by the time the LINQ query is actually evaluated (assuming it is eventually evaluated), the enclosing method will have been completed and the s variable will be 'out of scope', at least according to the traditional scope rules. Despite this, it is still 'safe' in this context to refer s.

Closures are very convenient and well-behaved in a traditional functional programming language where things are naturally immutable. And the fact that C# is foremost an imperative programming language, where variables are mutable by default, is the basis for the issue that results in this strange work-around.

By creating the intermediate variable within the scope of the loop, you are effectively instructing the compiler to allocate a separate, non-shared variable for each iteration of the LINQ query. Otherwise, each iteration will share the same instance of the variable, which will also be (obviously) the same value...probably not what you want.

稍尽春風 2024-09-15 18:03:00

该博文的评论之一:

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

One of the comments on that blog post:

However there's a bug in your first
version of the CartesianProduct method
: you're closing over the loop
variable, so, due to the deferred
execution, it makes the cartesian
product of the last sequence with
itself. You need to add a temporary
local variable inside the foreach loop
to make it work (the second version
works fine, though).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文