【发布时间】:2016-01-15 00:28:29
【问题描述】:
在 C# 中使用 lambda 表达式或匿名方法时,我们必须警惕 访问修改闭包 的陷阱。例如:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
由于修改了闭包,上面的代码将导致查询中的所有Where子句都基于s的最终值。
正如here 所解释的,发生这种情况是因为在上面的foreach 循环中声明的s 变量在编译器中是这样翻译的:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
而不是这样:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
正如here 指出的那样,在循环外声明变量并没有性能优势,在正常情况下,我能想到这样做的唯一原因是,如果您打算在循环:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
但是,foreach 循环中定义的变量不能在循环外使用:
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
因此,编译器声明变量的方式使其极易出现通常难以发现和调试的错误,同时不会产生明显的好处。
有没有什么你可以用foreach循环做的事情,如果它们是用内部范围的变量编译的,你就做不到,或者这只是在匿名方法和lambda表达式可用之前做出的任意选择还是普通的,从那以后没有修改过?
【问题讨论】:
-
String s; foreach (s in strings) { ... }有什么问题? -
@BradChristie OP 并不是真正在谈论
foreach,而是在谈论导致类似代码的 lamda 表达式,如 OP 所示... -
@BradChristie:可以编译吗? (错误:foreach 语句中需要类型和标识符对我来说)
-
@JakobBotschNielsen:它是 lambda 的封闭外部局部;你为什么假设它会在堆栈上?它的生命周期比栈帧长!
-
@EricLippert:我很困惑。我知道 lambda 捕获对 foreach 变量的引用(在循环内部声明 outside),因此您最终会与它的最终值进行比较;我明白了。我不明白的是,在循环中声明变量 inside 会有什么不同。从编译器编写者的角度来看,无论声明是在循环内部还是外部,我都只在堆栈上分配一个字符串引用(var's');我当然不想每次迭代都将新的引用推入堆栈!
标签: c# foreach lambda scope anonymous-methods