Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to tell a lambda function to capture a copy instead of a reference in C#?

I've been learning C#, and I'm trying to understand lambdas. In this sample below, it prints out 10 ten times.

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();
    }
}

Obviously, the generated class behind the lambda is storing a reference or pointer to the int i variable, and is assigning a new value to the same reference every time the loop iterates. Is there a way to force the lamda to grab a copy instead, like the C++0x syntax

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

vs.

[=](){ ... } // Capture copies
like image 513
Eclipse Avatar asked Jan 16 '09 20:01

Eclipse


4 Answers

What the compiler is doing is pulling your lambda and any variables captured by the lambda into a compiler generated nested class.

After compilation your example looks a lot like this:

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);                 }         } } 

By making a copy within the for loop, the compiler generates new objects in each iteration, like so:

for (int i = 0; i < 10; ++i) {     DisplayClass1 displayClass1 = new DisplayClass1();     displayClass1.i = i;     actions.Add(new Action(displayClass1.Lambda)); } 
like image 175
Tinister Avatar answered Sep 26 '22 10:09

Tinister


The only solution I've been able to find is to make a local copy first:

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

But I'm having trouble understanding why putting a copy inside the for-loop is any different than having the lambda capture i.

like image 24
Eclipse Avatar answered Sep 25 '22 10:09

Eclipse


The only solution is to make a local copy and reference that within the lambda. All variables in C# (and VB.Net) when accessed in a closure will have reference semantics vs. copy/value semantics. There is no way to change this behavior in either language.

Note: It doesn't actually compile as a reference. The compiler hoists the variable into a closure class and redirects accesses of "i" into a field "i" inside the given closure class. It's often easier to think of it as reference semantics though.

like image 23
JaredPar Avatar answered Sep 25 '22 10:09

JaredPar


Remember that lambda expressions are really only syntactic sugar for anonymous methods.

That being said, what you are really looking for is how anonymous methods use local variables in a parent scope.

Here's a link describing this. http://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx#4

like image 37
Matt Brunell Avatar answered Sep 22 '22 10:09

Matt Brunell