来自 Eric Lippert 的博客:“不要关闭循环变量”
来自 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
埃里克本人的几篇相关文章,以及评论中的一些有趣的讨论:
A couple of relevant articles from Eric himself, along with some interesting discussion in the comments:
这是一个微妙的范围问题,与闭包和延迟执行的工作方式有关。
如果不使用局部变量,而是直接序列,则结果 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.
此处使用的 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, theCartesianProduct
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 thes
variable will be 'out of scope', at least according to the traditional scope rules. Despite this, it is still 'safe' in this context to refers
.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.
该博文的评论之一:
One of the comments on that blog post: