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

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

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

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

正如here所解释的那样,这是因为在上面的foreach循环中声明的s变量在编译器中被翻译成这样:

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

而不是像这样:

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

正如here所指出的,在循环外声明变量没有性能优势,在正常情况下,我能想到这样做的唯一原因是你计划在循环范围之外使用变量:

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表达式可用或普通之前做出的任意选择,以及从那以后还没有修改过?

最佳答案

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.

你的批评是完全合理的.

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

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?

后者. C#1.0规范实际上没有说明循环变量是在循环体内部还是外部,因为它没有产生可观察到的差异.当在C#2.0中引入闭包语义时,选择将循环变量放在循环之外,与“for”循环一致.

我认为所有人都对这一决定感到遗憾是公平的.这是C#中最糟糕的“陷阱”之一,我们将采取突破性的改变来解决它.在C#5中,foreach循环变量将在逻辑上位于循环体内,因此闭包每次都会得到一个新的副本.

for循环不会被更改,并且更改不会“反向移植”到以前版本的C#.因此,在使用这个习语时你应该继续小心.

转载注明原文:C#在foreach中重用变量是否有原因? - 代码日志