代码之家  ›  专栏  ›  技术社区  ›  matt

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

  •  11
  • matt  · 技术社区  · 14 年前

    假设我们有以下代码:

    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被创建时 . i ,而不是拍摄 创建lambda时的值。

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

    而且, 惊喜 ,它输出 changed

    我使用的是XNA3.1,以及随附的C#的任何版本。


    我的问题是:

    1. lambda函数是否以某种方式存储对变量的“引用”或其他内容?
    5 回复  |  直到 5 年前
        1
  •  17
  •   Community CDub    8 年前

    这是一个修改过的闭包

    Access to Modified Closure

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

       foreach(AClass i in AClassCollection) 
       { 
          AClass anotherI= i;
          listOfLambdaFunctions.AddLast(  () =>  {  PrintLine(anotherI.name); }  ); 
       } 
    
        2
  •  14
  •   Eric Lippert    11 年前

    lambda函数是否以某种方式存储对变量的“引用”或其他内容?

    接近。lambda函数捕获 本身。不需要存储 参考 整变量 价值

    记住,变量是一个存储位置。名字“i”是指一个特定的存储位置,在你的例子中,它 总是 指的是 存放地点。

    这个问题有什么问题吗?

    对。每次通过循环创建一个新变量。然后闭包每次都捕获一个不同的变量。

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

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

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

        3
  •  5
  •   Marc Gravell    14 年前

    是的 确切地 变量 价值 变量的。通常可以通过引入一个临时变量并绑定到该变量来解决此问题:

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

    尤其地 在里面 foreach 这是臭名昭著的。

        4
  •  2
  •   Stack Overflow is garbage    14 年前

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

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

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

        5
  •  1
  •   Matthew Vines    14 年前

    我也被这个抓到了,正如卡尔加里编码所说,这是一个修改后的结束。我真的很难找到他们,直到我得到了雷沙普。因为这是resharper关注的警告之一,所以我更擅长将它们识别为我的代码。