在C#中使用lambda表达式或匿名方法时,我们必须警惕 对修改后的闭包 陷阱的 访问 。例如:
foreach (var s in strings) { query = query.Where(i => i.Prop == s); // access to modified closure ... }
由于修改后的闭包,上述代码将导致Where查询中的所有子句都基于的最终值s。
Where
s
正如解释在这里,这是因为该s变量在声明foreach环以上的编译器编译如下:
foreach
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ... }
而不是像这样:
while (enumerator.MoveNext()) { string s; s = enumerator.Current; ... }
如此处所指出的,在循环外声明变量没有性能优势,在正常情况下,我能想到的唯一原因是如果您打算在循环范围外使用变量:
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表达式可用或通用之前做出的任意选择,并且没有从那以后就没有修改过?
编译器以一种很容易出错的方式声明该变量,该错误通常很难查找和调试,同时不会产生明显的好处。
您的批评是完全有道理的。
我在这里详细讨论这个问题:
关闭循环变量被认为是有害的
使用foreach循环,是否可以通过内部作用域变量进行编译而无法做到?还是这只是在匿名方法和lambda表达式可用或通用之前做出的任意选择,并且此后没有进行过修改?
后者。实际上,C#1.0规范没有说明循环变量是在循环体内还是在循环体内,因为它没有明显的区别。在C#2.0中引入闭包语义时,已做出选择,将循环变量置于循环之外,与“ for”循环一致。
我认为可以说所有人都对该决定表示遗憾。这是C#中最糟糕的“陷阱”之一, 我们将进行重大更改来修复它。 在C#5中,foreach循环变量在逻辑上将 位于 循环体内,因此闭包每次都会获得新的副本。
该for循环将不会改变,并且改变不会是“向后移植”到C#的早期版本。因此,在使用此惯用语时,您应继续小心。
for