43

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
Eclipse
  • 44,851
  • 20
  • 112
  • 171
  • 1
    You may want to read [this article](http://csharpindepth.com/Articles/Chapter5/Closures.aspx), written by our very own Jon Skeet. – Joel Coehoorn Jan 16 '09 at 20:12
  • possible duplicate of [C# Captured Variable In Loop](http://stackoverflow.com/questions/271440/c-sharp-captured-variable-in-loop) – nawfal Nov 02 '13 at 06:35
  • 8
    I find it curious that most of the answers to this question are explaining capture semantics which is perfectly clear to the author of the question while only some mention the solution (temporary copy). Does noone read questions before answering? – ghord Nov 29 '17 at 10:37
  • It seems like C# lambdas capture references by reference `object o = new object(); Task.Run(() => Console.Write($"o is null: {o==null}");); o = null;` prints TRUE if the timing is right. This means you'd have to create a copy yourself and not modify it until lambda is executed to get the desired effect – Nenad Apr 04 '22 at 14:41

4 Answers4

35

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
  • 44,851
  • 20
  • 112
  • 171
  • 25
    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 Jan 16 '09 at 20:17
34

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
  • 11,097
  • 6
  • 35
  • 36
  • Thank you. This was really helpful in working out what was happening when the expression tried to evaluate the parameters. – Quark Soup Dec 03 '18 at 02:02
  • 7
    No offense to the author, but this shouldn't be the accepted answer. The question was about forcing C# to capture the variable by value and this answer only explains the mechanics. I still don't know how to capture by value, unless I am using `DisplayClass1` instead of a lambda (which, IMO defeats the purpose). – Robert F. Dec 15 '20 at 12:08
11

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.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
4

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
  • 10,141
  • 3
  • 34
  • 46