"Task.Factory.StartNew duplication issue" 意为“Task.Factory.StartNew 复制问题”。

12 浏览
0 Comments

"Task.Factory.StartNew duplication issue" 意为“Task.Factory.StartNew 复制问题”。

在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月20日
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#中闭包覆盖foreach变量

0
0 Comments

编译器以一种高度容易出错且难以找到和调试的方式声明变量,而不产生明显的好处。\n你的批评完全合理。\n我在这里详细讨论了这个问题:\n通过循环变量进行闭包可能有害(第一部分)\n有没有一些你可以用foreach循环这种方式做到的事情,如果它们编译为内部作用域变量,则无法实现?或者这只是一种在匿名方法和lambda表达式可用或常见之前就已经做出的任意选择,并且自那时以来没有进行过修订?\n是后者。实际上,C# 1.0规范并未说明循环变量是在循环体内部还是外部,因为这没有产生任何可观察的区别。当闭包语义被引入C# 2.0时,选择将循环变量放在循环外部,从而与for循环一致。\n我认为可以毫不客气地说,大家都为这个决定感到遗憾。这是C#中最糟糕的\"陷阱\"之一,我们将采取破坏性的改变来解决它。在C# 5中,foreach循环变量将逻辑上位于循环体内部,因此闭包每次都会得到一个新的副本。\nfor循环将不会改变,并且这种更改不会“向后移植”到以前版本的C#。因此,在使用这种习惯用法时,您应该继续小心。

0