此问题在此处已有答案:
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#行为中的一个缺陷?
4条答案
按热度按时间ipakzgxi1#
Eric本人的几篇相关文章,沿着评论中一些有趣的讨论:
krcsximq2#
这是一个微妙的范围问题,与闭包和延迟执行的工作方式有关。
如果不使用局部变量,而是直接使用sequence,则结果IEnumarable将绑定到VARIABLE序列而不是sequence的VALUE,并且在执行查询时,VARIABLE序列将包含sequence的LAST VALUE。
如果你像Eric的例子那样声明另一个局部变量,作用域被限制在每个循环迭代中,因此即使执行被延迟,它也会按预期被求值。
iibxawm43#
此处使用的LINQ查询导致
s
的值在其最初定义的范围之外可用(即CartesianProduct
方法)。这就是所谓的closure。由于延迟执行,在实际计算LINQ查询时(假设它最终被求值),则封闭的方法将已经完成,并且s
变量将“超出作用域”,至少根据传统的作用域规则是这样。在该上下文中引用s
仍然是“安全”。闭包在传统的函数式编程语言中非常方便,而且表现良好,因为在传统的函数式编程语言中,事物本质上是不可变的。而C#首先是一种命令式编程语言,变量在默认情况下是可变的,这一事实是导致这种奇怪的解决方案的问题的基础。
通过在循环范围内创建中间变量,可以有效地指示编译器为LINQ查询的每次迭代分配一个单独的非共享变量。否则,每次迭代都将共享该变量的同一示例,该示例的值(显然)也将相同......这可能不是您想要的。
mftmpeh84#
那篇博文上的评论之一是:
但是,在CartesianProduct方法的第一个版本中有一个bug:你关闭了循环变量,所以,由于延迟执行,它会产生最后一个序列与自身的笛卡尔乘积。你需要在foreach循环中添加一个临时的局部变量来使它工作(尽管第二个版本工作得很好)。