Categories
c# captured-variable closures

Captured variable in a loop in C#

268

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?

9

  • 16

    See also Eric Lippert’s Blog series on the subject: Closing over the Loop Variable Considered Harmful

    – Brian

    Nov 11, 2010 at 21:50


  • 11

    Also, they are changing C# 5 to work as you expect within a foreach. (breaking change)

    Mar 4, 2012 at 18:55

  • 1

  • 4

    @Neal: although this example still doesn’t work properly in C# 5, as it still outputs five 10s

    – Ian Oakes

    Feb 6, 2014 at 5:41

  • 7

    It verified that it outputs five 10s till today on C# 6.0 (VS 2015). I doubt that this behavior of closure variables is a candidate for change. Captured variables are always evaluated when the delegate is actually invoked, not when the variables were captured.

    – RBT

    Apr 22, 2017 at 3:03


247

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.

6

  • 37

    Jon’s book also has a very good chapter on this (stop being humble, Jon!)

    Nov 7, 2008 at 7:57

  • 44

    It looks better if I let other people plug it 😉 (I confess that I do tend to vote up answers recommending it though.)

    – Jon Skeet

    Nov 7, 2008 at 8:03

  • 2

    As ever, feedback to [email protected] would be appreciated 🙂

    – Jon Skeet

    Nov 7, 2008 at 9:30

  • 7

    For C# 5.0 behavior is different (more reasonable) see newer answer by Jon Skeet – stackoverflow.com/questions/16264289/…

    Jan 22, 2016 at 2:35


  • 2

    @Florimond: That’s just not how closures work in C#. They capture variables, not values. (That’s true regardless of loops, and is easily demonstrated with a lambda that captures a variable, and just prints the current value whenever it’s executed.)

    – Jon Skeet

    Apr 29, 2019 at 17:27


26

I believe what you are experiencing is something known as Closure http://en.wikipedia.org/wiki/Closure_(computer_science). Your lamba has a reference to a variable which is scoped outside the function itself. Your lamba is not interpreted until you invoke it and once it is it will get the value the variable has at execution time.

    15

    Behind the scenes, the compiler is generating a class that represents the closure for your method call. It uses that single instance of the closure class for each iteration of the loop. The code looks something like this, which makes it easier to see why the bug happens:

    void Main()
    {
        List<Func<int>> actions = new List<Func<int>>();
    
        int variable = 0;
    
        var closure = new CompilerGeneratedClosure();
    
        Func<int> anonymousMethodAction = null;
    
        while (closure.variable < 5)
        {
            if(anonymousMethodAction == null)
                anonymousMethodAction = new Func<int>(closure.YourAnonymousMethod);
    
            //we're re-adding the same function 
            actions.Add(anonymousMethodAction);
    
            ++closure.variable;
        }
    
        foreach (var act in actions)
        {
            Console.WriteLine(act.Invoke());
        }
    }
    
    class CompilerGeneratedClosure
    {
        public int variable;
    
        public int YourAnonymousMethod()
        {
            return this.variable * 2;
        }
    }
    

    This isn’t actually the compiled code from your sample, but I’ve examined my own code and this looks very much like what the compiler would actually generate.