是否有一个C#在foreach中重用该variables的原因?
在C#中使用lambdaexpression式或匿名方法时,我们必须警惕修改的闭包陷阱。 例如:
foreach (var s in strings) { query = query.Where(i => i.Prop == s); // access to modified closure ... }
由于修改了闭包,上面的代码将导致查询中的所有Where
子句基于s
的最终值。
正如在这里解释的,这是因为在foreach
循环中声明的s
variables在编译器中是这样翻译的:
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ... }
而不是像这样:
while (enumerator.MoveNext()) { string s; s = enumerator.Current; ... }
正如这里指出的那样,在循环之外声明一个variables没有任何性能优势,在正常情况下,我可以考虑的唯一原因是如果您打算在循环范围之外使用该variables:
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ... } var finalString = s;
然而,在foreach
循环中定义的variables不能在循环之外使用:
foreach(string s in strings) { } var finalString = s; // won't work: you're outside the scope.
所以编译器以这样的方式声明variables,使得它很容易出错,而这个错误通常很难被发现和debugging,而没有产生明显的好处。
有没有什么事情可以用foreach
循环这样做,如果它们是用一个内部variables范围的variables编译的,或者这只是在匿名方法和lambdaexpression式可用或通用之前做出的任意select,从那以后还没有被修改过?
编译器以一种非常容易出错的方式声明variables,而这种错误通常很难find和debugging,而没有产生明显的好处。
你的批评是完全合理的。
我在这里详细讨论这个问题:
closures循环variables被认为是有害的
有这样的foreach循环,你可以做,如果他们不能编译一个内部范围的variables? 或者这只是在匿名方法和lambdaexpression式可用或通用之前做出的一个任意select,并且从那以后还没有被修改过?
后者。 C#1.0规范实际上并没有说明循环variables是在循环体内部还是外部,因为它没有明显的区别。 当在C#2.0中引入闭包语义时,select将循环variables放在循环之外,与“for”循环一致。
我认为这是公平的说,所有的遗憾,决定。 这是C#中最糟糕的“陷阱”之一, 我们将采取突破性的改变来解决它。 在C#5中,foreach循环variables将在逻辑上位于循环体内,因此每次都会得到新的副本。
for
循环将不会被更改,并且更改不会被“back ported”到以前版本的C#中。 因此,在使用这个习语时,你应该小心谨慎。
Eric Lippert在他的博客文章中关注了被认为有害的循环variables及其续集。
对我来说,最有说服力的论点是,在每次迭代中都有一个新的variables与for(;;)
样式循环不一致。 你会希望有一个新的int i
在for (int i = 0; i < 10; i++)
每个迭代中?
这种行为最常见的问题是迭代variables的闭包,它有一个简单的解决方法:
foreach (var s in strings) { var s_for_closure = s; query = query.Where(i => i.Prop == s_for_closure); // access to modified closure
我的博客文章关于这个问题: closures在C#中的foreachvariables 。
被咬了这个,我有一个习惯,就是在最内层的范围里包含本地定义的variables,我用它来转移到任何闭包。 在你的例子中:
foreach (var s in strings) { query = query.Where(i => i.Prop == s); // access to modified closure
我做:
foreach (var s in strings) { string search = s; query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.
一旦你有了这个习惯,你可以在极less数情况下避免这种习惯。 说实话,我觉得我从来没有这样做过。
在C#5.0中,这个问题是固定的,你可以closures循环variables,并得到你期望的结果。
语言规范说:
8.8.4 foreach语句
(……)
表单的foreach语句
foreach (V v in x) embedded-statement
然后扩展为:
{ E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } }
(……)
v
在while循环中的位置对于embedded语句中发生的任何匿名函数如何捕获都很重要。 例如:int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) f = () => Console.WriteLine("First value: " + value); } f();
如果
v
被声明在while循环之外,它将在所有迭代中被共享,并且在for循环之后的值将是最终值13
,这是f
的调用将被打印的。 相反,因为每个迭代都有自己的variablesv
,所以在第一次迭代中由f
捕获的variables将继续保持值7
,这将被打印。 ( 注意:早期版本的C#在while循环之外声明了v
)