可能重复:
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#的行为中被认为是疣的程度?
答案 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循环内的局部变量 使它工作(第二个版本 但工作得很好。