C#在foreach中重用变量的原因是什么?

25 浏览
0 Comments

C#在foreach中重用变量的原因是什么?

在C#中使用Lambda表达式或匿名方法时,我们必须小心访问修改的闭包问题。例如:

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

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

此处所述,这是因为在上面的foreach循环中声明的s变量在编译器中被翻译成这样:

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表达式可用或常见之前做出的任意选择,自那时以来没有进行修改?

admin 更改状态以发布 2023年5月21日
0
0 Comments

你所问的问题在Eric Lippert的博客文章《关闭循环变量被认为是有害的》及其续篇中都有详细讲解。

对我而言,最有说服力的论点是,在每次迭代中拥有新变量将与for(;;)循环风格的一致性不符。你会期望在for (int i = 0; i < 10; i++)的每次迭代中都有一个新的int i吗?

这种行为最常见的问题是闭包迭代变量,但有一种简单的解决方法:

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

我关于这个问题的博客文章:在C#中闭包迭代变量

0
0 Comments

编译器的声明使变量极易出现错误,但很难找到和调试,同时也没有明显的好处。你的批评是完全合理的。我在这里详细讨论了这个问题:Closing over the loop variable considered harmful。\n\n通过这种方式,在foreach循环中会做一些你不能在内部范围变量编译时做的事情吗?还是这只是一种任意的选择,在匿名方法和Lambda表达式可用或普遍之前就做出的选择,从那时起就没有经过修订?\n\n是后者。C#1.0规范实际上没有说明循环变量是在循环体内还是外部,因为这没有可观察的差异。当在C#2.0中引入闭包语义时,选择将循环变量放在循环外部,以与“for”循环一致。\n\n我认为公平地说,所有人都对这个决定感到遗憾。这是C#中最糟糕的“坑”,因此我们将采取破坏性的变化来解决它。在C#5中,foreach循环变量将在逻辑上位于循环体内,因此每次都会得到一个新的副本。\n\nfor循环不会改变,并且这种改变不会被“回溯”到以前的C#版本。因此,在使用这种习惯用法时,您应该继续小心。

0