Search code examples
c#closurescaptured-variable

Captured variable in a loop in C#


I met an interesting issue about C#. I have code like below.

List<Func<int>> actions = new List<Func<int>>();

int variable = 0;
while (variable < 5)
{
    actions.Add(() => variable * 2);
    ++ variable;
}

foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}

I expect it to output 0, 2, 4, 6, 8. However, it actually outputs five 10s.

It seems that it is due to all actions referring to one captured variable. As a result, when they get invoked, they all have same output.

Is there a way to work round this limit to have each action instance have its own captured variable?


Solution

  • Yes - take a copy of the variable inside the loop:

    while (variable < 5)
    {
        int copy = variable;
        actions.Add(() => copy * 2);
        ++ variable;
    }
    

    You can think of it as if the C# compiler creates a "new" local variable every time it hits the variable declaration. In fact it'll create appropriate new closure objects, and it gets complicated (in terms of implementation) if you refer to variables in multiple scopes, but it works :)

    Note that a more common occurrence of this problem is using for or foreach:

    for (int i=0; i < 10; i++) // Just one variable
    foreach (string x in foo) // And again, despite how it reads out loud
    

    See section 7.14.4.2 of the C# 3.0 spec for more details of this, and my article on closures has more examples too.

    Note that as of the C# 5 compiler and beyond (even when specifying an earlier version of C#), the behavior of foreach changed so you no longer need to make local copy. See this answer for more details.