本文翻译自: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. 因此,在使用此惯用语时,您应继续小心。

C#在foreach中重用变量是否有原因?相关推荐

  1. Java的foreach中,变量加final的作用(for(final XXX xxx : xxxs))

    2019独角兽企业重金招聘Python工程师标准>>> 在阅读apache的commons-io过程中,阅读到下段的代码. final List<Comparator<F ...

  2. Python坑:bool是int的子类、列表循环中的变量泄露、lambda在闭包中会保存局部变量、重用全局变量

    bool是int的子类 a = True print isinstance(a, int) print True == 1 print False == 0 运行结果: True True True ...

  3. Java forEach中 Lambda Expr中的 final变量要求

    https://my.oschina.net/wadelau/blog/1859419 Java forEach中 Lambda Expr中的 final变量要求 Java8闭包 闭包是一个函数在创建 ...

  4. 面试:为什么foreach中不允许对元素进行add和remove

    来源 | 公众号 我是程序汪 阿粉的读者遇到了一个比较经典的面试题,也就是标题上说的,为什么 foreach 中不允许对元素进行 add 和 remove.阿粉就这个问题深入分析一下为什么不让使用 a ...

  5. Smarty中的变量

    Smarty中变量分为三类 1.PHP分配的变量 index.php [php] view plaincopy print? require('./include.php');   //加载Smart ...

  6. php 函数静态变量,php 函数中静态变量使用的问题实例分析

    本文实例讲述了php 函数中静态变量使用的问题.分享给大家供大家参考,具体如下: function msg() { static $a = 0; echo $a++, ' '; } msg(); ms ...

  7. php迭代什么意思,PHP中迭代变量的坑

    $a = array('a','b','c','d','e','f'); foreach($a as &$v){ } /** * 在第一次foreach中$v是迭代变量 * 循环进行了6次 * ...

  8. Oracle入门(十四.4)之在PL / SQL中使用变量

    一.变量的使用 (1)使用变量的原因 •临时存储数据 •储存值的操作 •可重用性 (2)处理PL / SQL中的变量 变量是: •在声明部分声明并初始化 •在可执行部分中使用并分配新值 变量可以是: ...

  9. css中变量_CSS中的变量

    css中变量 CSS | 变数 (CSS | Variables) CSS variables allow you to create reusable values that can be used ...

最新文章

  1. Linux系统开机过程详细分析
  2. 耕牛传媒关于诈骗,拖延工期等负面信息特别申明
  3. 为梦想而战,高考励志主题教育班会PPT
  4. 用C#打开文件对话框的方法和简单使用的程序
  5. 超详细尚硅谷mysql 分组函数
  6. java实现复原IP地址_LeetCode 力扣 93. 复原IP地址
  7. 190217每日一句
  8. BootStrap日历插件
  9. 虚拟仿真实验平台服务器需求,虚拟仿真实验中心平台建设方案.pptx
  10. AOC显示器OSD已锁是什么意思?怎么解锁?
  11. 23种设计模式:(一)创建者模型
  12. java实现图片压缩
  13. 帝国cms后台登录系统限制次数,60分钟过后重新登录解决办法
  14. 五一假期维修手机感想
  15. 微服务基础知识点学习笔记(持续更新)
  16. java8高级应用与开发课件和贯穿案例(全)分享
  17. ubuntu进入显示:emergency mode
  18. 同轴电缆、双绞线和光纤光缆有什么区别?
  19. python 微信公众号网页接口调用_Python调用微信公众平台接口操作示例
  20. 各路大神对于观测器的文章总结【持续更新】

热门文章

  1. Nginx安装及配置文件解释
  2. C++开源代码覆盖率工具OpenCppCoverage介绍(Windows)
  3. Struts中ActionActionForm
  4. MySQL服务器状态变量
  5. SocketDataHandler ConcurrentAsyncQueue Server/Client
  6. 多个ajax执行混乱问题
  7. 测者的测试技术手册:AI的自动化单元测试
  8. 千氪|比特币十周年大事记
  9. selenium grid原理
  10. [LAMP]——mod_security和mod_evasive模块的安装