来自Eric Lippert的博客:“不要关闭循环变量”
可能重复:
为什么在lambda表达式中使用迭代变量很糟糕
C# – foreach标识符和闭包
来自Eric Lippert的2010年6月28日报道 :
static IEnumerable<IEnumerable> CartesianProduct(this IEnumerable<IEnumerable> sequences) { // base case: IEnumerable<IEnumerable> result = new[] { Enumerable.Empty() }; 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#的行为中,这在多大程度上被视为疣?
Eric自己的一些相关文章,以及评论中的一些有趣的讨论:
- 关闭循环变量被认为是有害的
- 关闭循环变量,第二部分
这是一个微妙的范围问题,与闭包和延迟执行的工作方式有关。
如果不使用局部变量,而是直接序列,则结果IEnumarable绑定到VARIABLE序列而不是序列的VALUE,并且在执行查询时,VARIABLE序列包含序列的最后值。
如果您在Eric的示例中声明另一个局部变量,则范围仅限于每个循环迭代。 因此,即使执行被推迟,也将按预期进行评估。
这里使用的LINQ查询导致s
的值在最初定义的范围之外(即CartesianProduct
方法)可用。 这就是所谓的闭包 。 由于执行延迟,在实际评估LINQ查询时(假设它最终被评估),封闭方法将完成并且s
变量将“超出范围”,至少根据传统的范围规则。 尽管如此,在这种背景下引用s
仍然是“安全的”。
闭包非常方便,并且在传统的函数式编程语言中表现良好,其中事物自然是不可变的。 事实上,C#最重要的是一种命令式编程语言,默认情况下变量是可变的,这个问题的基础导致了这种奇怪的解决方法。
通过在循环范围内创建中间变量,您可以有效地指示编译器为LINQ查询的每次迭代分配一个单独的非共享变量。 否则,每次迭代将共享变量的相同实例,这也将(显然)是相同的值…可能不是你想要的。
该博客文章的评论之一:
但是,你的CartesianProduct方法的第一个版本中存在一个错误:你正在关闭循环变量,因此,由于延迟执行,它会使最后一个序列的笛卡尔积与自身相关。 您需要在foreach循环中添加一个临时局部变量以使其工作(但第二个版本工作正常)。