2009-01-16 99 views
18

我一直在学习C#,并且我正试图理解lambda。在下面的这个例子中,它打印出10次10​​次。如何告诉lambda函数在C#中捕获副本而不是参考?

class Program 
{ 
    delegate void Action(); 
    static void Main(string[] args) 
    { 
     List<Action> actions = new List<Action>(); 

     for (int i = 0; i < 10; ++i) 
      actions.Add(()=>Console.WriteLine(i)); 

     foreach (Action a in actions) 
      a(); 
    } 
} 

显然,拉姆达后面生成的类被存储的引用或指针int i可变的,并且每当分配一个新的值,以相同的附图的循环迭代。有没有办法迫使LAMDA抢副本,而不是一样的C++ 0x语法

[&](){ ... } // Capture by reference 

[=](){ ... } // Capture copies 
+0

您可能想要阅读[本文](http://csharpindepth.com/Articles/Chapter5/Closures.aspx),由我们自己的Jon Skeet编写。 – 2009-01-16 20:12:41

+0

[C#捕获变量循环]可能的重复(http://stackoverflow.com/questions/271440/c-sharp-captured-variable-in-loop) – nawfal 2013-11-02 06:35:41

回答

18

编译器正在做的是将您的lambda和由lambda捕获的任何变量拉入编译器生成的嵌套类。

编译后您的例子看起来很像这样的:

class Program 
{ 
     delegate void Action(); 
     static void Main(string[] args) 
     { 
       List<Action> actions = new List<Action>(); 

       DisplayClass1 displayClass1 = new DisplayClass1(); 
       for (displayClass1.i = 0; displayClass1.i < 10; ++displayClass1.i) 
         actions.Add(new Action(displayClass1.Lambda)); 

       foreach (Action a in actions) 
         a(); 
     } 

     class DisplayClass1 
     { 
       int i; 
       void Lambda() 
       { 
         Console.WriteLine(i); 
       } 
     } 
} 

通过内部的for循环进行复印时,编译器在每次迭代中生成新的对象,像这样:

for (int i = 0; i < 10; ++i) 
{ 
    DisplayClass1 displayClass1 = new DisplayClass1(); 
    displayClass1.i = i; 
    actions.Add(new Action(displayClass1.Lambda)); 
} 
7

我已经能够找到的唯一解决方案是使本地备份:

for (int i = 0; i < 10; ++i) 
{ 
    int copy = i; 
    actions.Add(() => Console.WriteLine(copy)); 
} 

但我无法理解为什么把一个副本的for循环中比具有拉姆达捕获i任何不同。

9

唯一的解决办法是在lambda内做一个本地拷贝和引用。在闭包中访问时,C#(和VB.Net)中的所有变量都将具有引用语义与复制/值语义。无论哪种语言都无法改变这种行为。

注意:它实际上并未作为参考进行编译。编译器将变量提升为闭包类,并在给定闭包类中将“i”的访问重定向到字段“i”。尽管如此,将其视为参考语义通常更容易。