C#lambda,当你想到的时候没有采用局部变量值?

时间:2010-11-11 14:56:22

标签: c# lambda

假设:

void AFunction()
{

   foreach(AClass i in AClassCollection)
   {
      listOfLambdaFunctions.AddLast(  () =>  {  PrintLine(i.name); }  );
   }
}

void Main()
{
    AFunction();
    foreach( var i in listOfLambdaFunctions)
       i();
}

现在你会认为这样做会对等:

void Main()
{

    foreach(AClass i in AClassCollection)
       PrintLine(i.name);
}

但它没有,它将做的是每次打印AClassCollection中最后一项的名称! 所以基本上每个lambda函数都使用相同的项目。我怀疑“当lambda被创建时”或“当它使用其中使用的外部变量的快照时”可能会有一些延迟,或者基本上只是持有'对局部变量的引用'i'

所以我这样做了:

string astr = "a string";
AFunc fnc = () => { System.Diagnostics.Debug.WriteLine(astr); };
astr = "chagnged!";
fnc();
惊讶,惊讶,它输出“改变了!”

我正在使用XNA 3.1(无论是什么c#)

发生了什么事? lambda函数以某种方式存储变量或其他东西的“引用”吗? 反正这个问题呢?

5 个答案:

答案 0 :(得分:17)

这是一个修改后的闭包

请参阅:Access to Modified Closure

等类似问题

要解决此问题,您必须在for循环的范围内存储变量的副本:

   foreach(AClass i in AClassCollection) 
   { 
      AClass anotherI= i;
      listOfLambdaFunctions.AddLast(  () =>  {  PrintLine(anotherI.name); }  ); 
   } 

答案 1 :(得分:14)

  

lambda函数是否以某种方式存储了对变量的“引用”或什么?

关闭。 lambda函数捕获变量本身。不需要将引用存储到变量中,事实上,在.NET中永久存储对变量的引用是不可能的。您只需捕获整个变量。您永远不会捕获变量的

请记住,变量是存储位置。名称“i”指的是特定的存储位置,在您的情况下,始终指的是相同的存储位置。

  

还有这个问题吗?

是。每次循环创建一个新变量。然后,闭包每次都会捕获一个不同的变量。

这是C#最常报告的问题之一。我们正在考虑更改循环变量声明的语义,以便每次通过循环创建一个新变量。

有关此问题的更多详细信息,请参阅我关于此主题的文章:

http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/

答案 2 :(得分:5)

  

发生了什么事? lambda函数以某种方式存储变量或其他东西的“引用”吗?

完全那个; c#捕获的变量是变量,而不是变量的。你通常可以通过引入一个临时变量并绑定到它来解决这个问题:

string astr = "a string";
var tmp = astr;
AFunc fnc = () => { System.Diagnostics.Debug.WriteLine(tmp); };

尤其是<{1}}中的,这是臭名昭着的。

答案 3 :(得分:2)

是的,lambda存储对变量的引用(概念上讲,无论如何)。

一个非常简单的解决方法是:

 foreach(AClass i in AClassCollection)
   {
      AClass j = i;
      listOfLambdaFunctions.AddLast(  () =>  {  PrintLine(j.name); }  );
   }

在foreach循环的每次迭代中,都会创建一个 new j,lambda会捕获它。 另一方面,i始终是相同的变量,但每次迭代都会更新(因此所有lambda最终都会看到最后一个值)

我同意这有点令人惊讶。 :)

答案 4 :(得分:1)

我也被这个人抓住了,正如卡尔加里·科德所说的那样,这是一个改进的封闭。在我得到resharper之前,我真的很难发现它们。由于这是resharper监视的警告之一,因此我更善于识别它们。我们编码。