c# - 中lambda - 异步lambda




是否有理由让C#在foreach中重用该变量? (3)

在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表达式可用或通用之前做出的任意选择,从那时起没有被修改过?


编译器以某种方式声明变量,使其非常容易出现通常难以发现和调试的错误,同时不会产生可感知的好处。

你的批评是完全合理的。

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

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

有没有什么你可以用foreach循环这样做,你不能用一个内部作用域变量编译? 或者这只是在匿名方法和lambda表达式可用或共同之前做出的一个任意选择,并且从那时起还没有被修改过?

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

我认为这是公平的说,所有的遗憾,决定。 这是C#中最糟糕的“陷阱”之一, 我们将采取重大改变来修复它。 在C#5中,foreach循环变量将在逻辑上位于循环体内,因此闭包每次都会得到全新的副本。

for循环将不会被更改,并且更改不会被“back ported”到先前版本的C#。 因此,你应该在使用这个习语时要小心。


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#中关闭foreach变量


被这个叮咬了,我有一个习惯,就是在最内层的范围内包含局部定义的变量,我用它来转移到任何闭包。 在你的例子中:

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

我做:

foreach (var s in strings)
{
    string search = s;
    query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.

一旦你有了这种习惯,你可以在少数情况下避免它,你实际上想要绑定到外部示波器。 说实话,我认为我从来没有这样做过。





anonymous-methods