是否有一个C#在foreach中重用该variables的原因?

在C#中使用lambdaexpression式或匿名方法时,我们必须警惕修改的闭包陷阱。 例如:

foreach (var s in strings) { query = query.Where(i => i.Prop == s); // access to modified closure ... } 

由于修改了闭包,上面的代码将导致查询中的所有Where子句基于s的最终值。

正如在这里解释的,这是因为在foreach循环中声明的svariables在编译器中是这样翻译的:

 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 ifor (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