views:

369

answers:

5

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
A: 

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.

Eclipse
Because the declaration of the int is inside the for loop, so it gets re-created every time. There are 10 different ints all named "copy", where there is only one int named "i", within the scope that gets curried.
technophile
+2  A: 

You may want to read this article, written by our very own Jon Skeet.

Joel Coehoorn
Thanks! That's hillarious - his examples are almost identical to mine!
Eclipse
+4  A: 

The only solution is to make a local coopy 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.

JaredPar
+1  A: 

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

Matt Brunell
+3  A: 

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