一尘不染

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

c#

在C#中使用lambda表达式或匿名方法时,我们必须警惕 对修改后的闭包 陷阱的 访问 。例如:

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

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

正如解释在这里,这是因为该s变量在声明foreach环以上的编译器编译如下:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}

而不是像这样:

while (enumerator.MoveNext())
{
   string s;
   s = enumerator.Current;
   ...
}

如此处所指出的,循环外声明变量没有性能优势,在正常情况下,我能想到的唯一原因是如果您打算在循环范围外使用变量:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
var finalString = s;

但是,foreach循环中定义的变量不能在循环外使用:

foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.

因此,编译器以某种方式声明该变量,使其极易出现通常难以查找和调试的错误,同时不会产生明显的收益。

是否可以通过foreach这种方式对循环执行某些操作,如果它们是使用内部作用域变量进行编译则无法做到的,或者这只是在匿名方法和lambda表达式可用或通用之前做出的任意选择,并且没有从那以后就没有修改过?


阅读 243

收藏
2020-05-19

共1个答案

一尘不染

编译器以一种很容易出错的方式声明该变量,该错误通常很难查找和调试,同时不会产生明显的好处。

您的批评是完全有道理的。

我在这里详细讨论这个问题:

关闭循环变量被认为是有害的

使用foreach循环,是否可以通过内部作用域变量进行编译而无法做到?还是这只是在匿名方法和lambda表达式可用或通用之前做出的任意选择,并且此后没有进行过修改?

后者。实际上,C#1.0规范没有说明循环变量是在循环体内还是在循环体内,因为它没有明显的区别。在C#2.0中引入闭包语义时,已做出选择,将循环变量置于循环之外,与“
for”循环一致。

我认为可以说所有人都对该决定表示遗憾。这是C#中最糟糕的“陷阱”之一, 我们将进行重大更改来修复它。 在C#5中,foreach循环变量在逻辑上将
位于 循环体内,因此闭包每次都会获得新的副本。

for循环将不会改变,并且改变不会是“向后移植”到C#的早期版本。因此,在使用此惯用语时,您应继续小心。

2020-05-19