C#在foreach中重用变量是否有原因?

本文翻译自:Is there a reason for C#'s reuse of the variable in a foreach?

When using lambda expressions or anonymous methods in C#, we have to be wary of the access to modified closure pitfall. 在C#中使用lambda表达式或匿名方法时,我们必须警惕对修改后的闭包陷阱的访问 For example: 例如:

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 Where clauses on the query to be based on the final value of s . 由于修改了闭包,上面的代码将使查询上的所有Where子句都基于s的最终值。

As explained here , this happens because the s variable declared in foreach loop 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 foreach loop 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 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表达式可用或通用之前做出的任意选择,以及从那以后没有修改过?


#1楼

参考:https://stackoom.com/question/bL13/C-在foreach中重用变量是否有原因


#2楼

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 8.8.4 foreach语句

(...) (......)

A foreach statement of the form 形式的foreach语句

 foreach (V v in x) embedded-statement 

is 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 v inside the while loop is important for how it is captured by any anonymous function occurring in the embedded-statement. v在while循环中的位置对于嵌入式语句中出现的任何匿名函数如何捕获它很重要。 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 v was 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 of f would print. 如果v在while循环之外声明,它将在所有迭代之间共享,并且for循环之后的值将是最终值13 ,这是对f的调用的输出。 Instead, because each iteration has its own variable v , the one captured by f in the first iteration will continue to hold the value 7 , which is what will be printed. 相反,因为每个迭代都有其自己的变量v ,所以在第一次迭代中被f捕获的变量将继续保持值7 ,该值将被打印。 ( Note: earlier versions of C# declared v outside of the while loop. ) 注意:C#的早期版本在while循环之外声明了v


#3楼

In my opinion it's strange question. 我认为这是一个奇怪的问题。 It's good to know how compiler works but that only "good to know". 知道编译器的工作原理是件好事,但只有“要知道”。

If you write code that depends on algorithm of compiler it's bad practice.And it's better to rewrite code to exclude this dependency. 如果您编写依赖于编译器算法的代码,这是一个坏习惯,那么最好重写代码以排除这种依赖关系。

This is good question for job interview. 这是求职面试的好问题。 But in real life I'v not faced with any problems that I solved on job interview. 但是在现实生活中,我没有遇到面试中解决的任何问题。

The 90% of foreach uses for process each element of collection (not for select or calculate some values). foreach的90%用于处理集合的每个元素(不适用于选择或计算某些值)。 sometimes you need to compute some values inside the loop but it's not good practice to create BIG loop. 有时您需要在循环内计算一些值,但是创建BIG循环不是一个好习惯。

It's better to use LINQ expressions for computing the values. 最好使用LINQ表达式来计算值。 Because when you calculate a lot of thing inside the loop, after 2-3 month when you (or anybody else) will read this code person will not understand what is this and how it should works. 因为当您在循环中计算出很多东西时,在2-3个月后您(或其他任何人)将阅读此代码时,人员将无法理解这是什么以及它应该如何工作。


#4楼

What you are asking is thoroughly covered by Eric Lippert in his blog post Closing over the loop variable considered harmful and its sequel. 埃里克·利珀特(Eric Lippert)在他的博客文章中完全涵盖了您所要问的问题: 关闭被认为有害的循环变量及其后继。

For me, the most convincing argument is that having new variable in each iteration would be inconsistent with for(;;) style loop. 对我来说,最有说服力的论据是,每次迭代中都有新变量与for(;;)样式循环不一致。 Would you expect to have a new int i in each iteration of for (int i = 0; i < 10; i++) ? 您是否希望for (int i = 0; i < 10; i++)每次迭代都具有一个新的int 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# . 我的博客文章有关此问题: C#中的foreach变量关闭


#5楼

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 very rare case you actually intended to bind to the outer scopes. 一旦有了这种习惯,在实际打算绑定到外部范围的少数情况下,就可以避免这种习惯。 To be honest, I don't think I have ever done so. 老实说,我认为我从未这样做过。


#6楼

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? 使用foreach循环,是否可以通过内部作用域变量进行编译而无法做到? 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? 还是这只是在匿名方法和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. 实际上,C#1.0规范没有说明循环变量是在循环体内还是在循环体内,因为它没有明显的区别。 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#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. 这是C#中最糟糕的“陷阱”之一, 我们将进行重大更改来修复它。 In C# 5 the foreach loop variable will be logically inside the body of the loop, and therefore closures will get a fresh copy every time. 在C#5中,foreach循环变量在逻辑上将位于循环体内,因此闭包每次都会获得新的副本。

The for loop will not be changed, and the change will not be "back ported" to previous versions of C#. for循环将不会更改,并且更改不会“反向移植”到以前的C#版本。 You should therefore continue to be careful when using this idiom. 因此,在使用此惯用语时,您应继续小心。

发布了0 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/p15097962069/article/details/105200833