C#在foreach中重用变量的原因是什么?
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表达式可用或常见之前做出的任意选择,自那时以来没有进行修改?
你所问的问题在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#中闭包迭代变量。
编译器的声明使变量极易出现错误,但很难找到和调试,同时也没有明显的好处。你的批评是完全合理的。我在这里详细讨论了这个问题: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#版本。因此,在使用这种习惯用法时,您应该继续小心。