Coder Perfect

In C#, a captured variable is used in a loop.

Problem

I came into an intriguing C# problem. I have the following code.

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’m anticipating 0, 2, 4, 6, and 8 as outputs. It, on the other hand, produces five tens.

It appears that this is related to the fact that all actions refer to a single captured variable. As a result, when they’re called, they all produce the identical outcome.

Is there a way to get around this restriction by giving each action instance its own captured variable?

Asked by Morgan Cheng

Solution #1

Yes, inside the loop, make a copy of the variable:

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

When the C# compiler encounters a variable declaration, it acts as if it is creating a “new” local variable. It will, in fact, generate appropriate new closure objects, and it will become more involved (in terms of implementation) if you refer to variables in many scopes, but it will still work:)

It’s worth noting that using for or foreach is a more regular occurrence of this problem:

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

More information may be found in section 7.14.4.2 of the C# 3.0 specification, as well as in my article on closures.

Note that the behavior of foreach changed with the C# 5 compiler and beyond (even when specifying an earlier version of C#), thus you no longer need to make a local copy. For further information, see this answer.

Answered by Jon Skeet

Solution #2

Closure (computer science) is what I suppose you are feeling. http://en.wikipedia.org/wiki/Closure (computer science) Your lamba contains a reference to a variable that exists beyond the scope of the function. Your lamba will not be interpreted until you call it, and when it is, it will return the value of the variable at the moment of execution.

Answered by TheCodeJunkie

Solution #3

The compiler creates a class that represents the closure for your method call behind the scenes. For each iteration of the loop, it uses that single instance of the closure class. This is how the code appears, which makes it easy to see why the error occurs:

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 the compiled code from your sample, but I looked at my own code and it appears quite similar to what the compiler would produce.

Answered by gerrard00

Solution #4

You can get around this by storing the value you require in a proxy variable and capturing that variable.

I.E.

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

Answered by Tyler Levine

Solution #5

This happens when you use a lambda expression () => variable * 2 with an outer scoped variable that isn’t defined in the lambda’s inner scope.

Lambda expressions (in C#3+) and anonymous methods (in C#2) continue to generate actual methods. The difficulties of passing variables to these methods (pass by value? pass through via reference? By reference is used in C#, however this introduces a new problem: the reference can outlive the actual variable. To alleviate all of these problems, C# creates a new helper class called closure, which has fields that correspond to the local variables used in lambda expressions and methods that correspond to the lambda methods themselves. Any changes you make to variables in your code are reflected in ClosureClass.variable.

So, until the ClosureClass.variable reaches 10, your while loop keeps updating it, and then your for loops execute the actions, which all operate on the same ClosureClass.variable.

You must construct a separation between the loop variable and the variable that is being closed in order to get the desired outcome. You can achieve this by adding a new variable, such as:

List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
    var t = variable; // now t will be closured (i.e. replaced by a field in the new class)
    actions.Add(() => t * 2);
    ++variable; // changing variable won't affect the closured variable t
}
foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}

To accomplish this distinction, you may also relocate the closure to another method:

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

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

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

Mult can be used as a lambda expression (implicit closure)

static Func<int> Mult(int i)
{
    return () => i * 2;
}

Alternatively, you might use a real-world assistance class:

public class Helper
{
    public int _i;
    public Helper(int i)
    {
        _i = i;
    }
    public int Method()
    {
        return _i * 2;
    }
}

static Func<int> Mult(int i)
{
    Helper help = new Helper(i);
    return help.Method;
}

In any case, “closures” are not a concept connected to loops, but rather to the use of local scoped variables by anonymous methods / lambda expressions – however some careless use of loops does exhibit closures traps.

Answered by Maverick Meerkat

Post is based on https://stackoverflow.com/questions/271440/captured-variable-in-a-loop-in-c-sharp