C# 是否有理由在 foreach 中重用变量?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8898925/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a reason for C#'s reuse of the variable in a foreach?
提问by StriplingWarrior
When using lambda expressions or anonymous methods in C#, we have to be wary of the access to modified closurepitfall. For example:
在 C# 中使用 lambda 表达式或匿名方法时,我们必须警惕访问修改后的闭包陷阱。例如:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
Due to the modified closure, the above code will cause all of the Whereclauses on the query to be based on the final value of s.
由于修改了闭包,上面的代码会导致Where查询的所有子句都基于 的最终值s。
As explained here, this happens because the svariable declared in foreachloop above is translated like this in the compiler:
正如这里所解释的,发生这种情况是因为上面循环中s声明的变量foreach在编译器中是这样翻译的:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
instead of like this:
而不是这样:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
As pointed out here, there are no performance advantages to declaring a variable outside the loop, and under normal circumstances the only reason I can think of for doing this is if you plan to use the variable outside the scope of the loop:
正如这里所指出的,在循环外声明变量没有性能优势,在正常情况下,我能想到的唯一原因是如果您打算在循环范围外使用变量:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
However variables defined in a foreachloop cannot be used outside the loop:
但是在foreach循环中定义的变量不能在循环外使用:
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
So the compiler declares the variable in a way that makes it highly prone to an error that is often difficult to find and debug, while producing no perceivable benefits.
因此,编译器以某种方式声明变量,使其极易出现通常难以查找和调试的错误,同时不会产生任何明显的好处。
Is there something you can do with foreachloops this way that you couldn't if they were compiled with an inner-scoped variable, or is this just an arbitrary choice that was made before anonymous methods and lambda expressions were available or common, and which hasn't been revised since then?
foreach如果循环是使用内部作用域变量编译的,那么您是否可以通过这种方式对循环执行某些操作,或者这只是在匿名方法和 lambda 表达式可用或通用之前做出的任意选择,并且没有从那以后没有被修改过吗?
采纳答案by Eric Lippert
The compiler declares the variable in a way that makes it highly prone to an error that is often difficult to find and debug, while producing no perceivable benefits.
编译器以某种方式声明变量,使其极易出现通常难以查找和调试的错误,同时不会产生明显的好处。
Your criticism is entirely justified.
你的批评是完全有道理的。
I discuss this problem in detail here:
我在这里详细讨论这个问题:
Closing over the loop variable considered harmful
Is there something you can do with foreach loops this way that you couldn't if they were compiled with an inner-scoped variable? or is this just an arbitrary choice that was made before anonymous methods and lambda expressions were available or common, and which hasn't been revised since then?
如果使用内部作用域变量编译它们,您是否可以通过这种方式对 foreach 循环执行某些操作?或者这只是在匿名方法和 lambda 表达式可用或通用之前做出的任意选择,从那时起就没有修改过?
The latter. The C# 1.0 specification actually did not say whether the loop variable was inside or outside the loop body, as it made no observable difference. When closure semantics were introduced in C# 2.0, the choice was made to put the loop variable outside the loop, consistent with the "for" loop.
后者。C# 1.0 规范实际上并没有说明循环变量是在循环体内部还是外部,因为它没有明显的区别。在 C# 2.0 中引入闭包语义时,选择将循环变量放在循环之外,与“for”循环一致。
I think it is fair to say that all regret that decision. This is one of the worst "gotchas" in C#, and we are going to take the breaking change to fix it.In C# 5 the foreach loop variable will be logically insidethe body of the loop, and therefore closures will get a fresh copy every time.
我认为可以公平地说,所有人都对那个决定感到遗憾。这是 C# 中最糟糕的“陷阱”之一,我们将采取重大更改来修复它。在 C# 5 中,foreach 循环变量将在逻辑上位于循环体内部,因此闭包每次都会获得一个新副本。
The forloop will not be changed, and the change will not be "back ported" to previous versions of C#. You should therefore continue to be careful when using this idiom.
该for循环将不会改变,并且改变不会是“向后移植”到C#的早期版本。因此,您在使用这个习语时应该继续小心。
回答by Krizz
What you are asking is thoroughly covered by Eric Lippert in his blog post Closing over the loop variable considered harmfuland its sequel.
Eric Lippert 在他的博客文章Closing over the loop variable 被认为是有害的及其续篇中彻底涵盖了您要问的问题。
For me, the most convincing argument is that having new variable in each iteration would be inconsistent with for(;;)style loop. Would you expect to have a new int iin each iteration of for (int i = 0; i < 10; i++)?
对我来说,最有说服力的论点是在每次迭代中使用新变量会与for(;;)样式循环不一致。您是否希望int i在每次迭代中都有一个新的for (int i = 0; i < 10; i++)?
The most common problem with this behavior is making a closure over iteration variable and it has an easy workaround:
这种行为最常见的问题是对迭代变量进行闭包,它有一个简单的解决方法:
foreach (var s in strings)
{
var s_for_closure = s;
query = query.Where(i => i.Prop == s_for_closure); // access to modified closure
My blog post about this issue: Closure over foreach variable in C#.
我关于这个问题的博文:Closure over foreach variable in C#。
回答by Godeke
Having been bitten by this, I have a habit of including locally defined variables in the innermost scope which I use to transfer to any closure. In your example:
受此影响,我习惯于将本地定义的变量包含在我用来转移到任何闭包的最内层作用域中。在你的例子中:
foreach (var s in strings)
query = query.Where(i => i.Prop == s); // access to modified closure
I do:
我愿意:
foreach (var s in strings)
{
string search = s;
query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.
}
Once you have that habit, you can avoid it in the veryrare case you actually intended to bind to the outer scopes. To be honest, I don't think I have ever done so.
一旦你有了这个习惯,你就可以在非常罕见的情况下避免它,你实际上打算绑定到外部作用域。老实说,我认为我从来没有这样做过。
回答by Paolo Moretti
In C# 5.0, this problem is fixed and you can close over loop variables and get the results you expect.
在 C# 5.0 中,此问题已修复,您可以关闭循环变量并获得您期望的结果。
The language specification says:
语言规范说:
8.8.4 The foreach statement
(...)
A foreach statement of the form
foreach (V v in x) embedded-statementis then expanded to:
{ E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } }(...)
The placement of
vinside the while loop is important for how it is captured by any anonymous function occurring in the embedded-statement. For example:int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) f = () => Console.WriteLine("First value: " + value); } f();If
vwas declared outside of the while loop, it would be shared among all iterations, and its value after the for loop would be the final value,13, which is what the invocation offwould print. Instead, because each iteration has its own variablev, the one captured byfin the first iteration will continue to hold the value7, which is what will be printed. (Note: earlier versions of C# declaredvoutside of the while loop.)
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 } }(……)
vwhile 循环内部的放置对于嵌入语句中出现的任何匿名函数如何捕获它很重要。例如: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将打印的内容。相反,因为每次迭代都有自己的变量v,f在第一次迭代中捕获的变量将继续保存值7,这就是将被打印的内容。(注意:早期版本的 C#v在 while 循环之外声明。)

