Solving the Issue of Captured Variable in a Loop in C#

When working with loops and closures in C#, you may encounter the issue of a captured variable. This can cause unexpected behavior and produce incorrect output. In this article, we will explore what captured variables are, why they can cause problems, and how to work around this issue to ensure each action instance has its own captured variable.

Understanding Captured Variables

In C#, a captured variable is a local variable that is referenced by a lambda expression or an anonymous method defined within its scope. When a lambda expression or an anonymous method captures a variable, it creates a closure that keeps the variable alive, even after its original scope has exited.

The Problem with Captured Variables in Loops

In the provided code example:


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

        

The expectation is to output 0, 2, 4, 6, 8. However, the actual output is five 10s. This behavior is due to all actions referring to the same captured variable. When the actions are invoked, they all use the current value of the shared captured variable, which is 5.

Solving the Issue

To work around the issue of a captured variable in a loop, you can create a new variable inside the loop and assign the value of the original variable to it. This ensures that each action instance will have its own captured variable:


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

for (int i = 0; i < 5; i++)
{
    int localVar = i;
    actions.Add(() => localVar * 2);
}

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

        

By creating a new localVar inside the loop and assigning the value of i to it, each action instance will have its own captured variable. The output will now be 0, 2, 4, 6, 8 as expected.

Explanation

When the loop iterates, a new localVar is created with a new memory location for each action instance. Since the lambda expression captures the local variable localVar, it keeps the value of localVar at the time of creation. Therefore, each action instance references its own captured variable.

Additional Example

Let's consider another example where we want to create a list of actions that add a specific value to a number:


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

int valueToAdd = 10;
for (int i = 0; i < 5; i++)
{
    int localVar = i;
    actions.Add((x) => x + localVar + valueToAdd);
}

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

        

In this example, the expectation is to output 15, 16, 17, 18, 19. Each action instance should add the value of localVar and valueToAdd to the given input. The output is the sum of the input, the loop variable, and the fixed value of 10.

Conclusion

The issue of a captured variable in a loop can cause unexpected behavior and incorrect output. By creating a new variable inside the loop and assigning the value of the original variable to it, you can ensure that each action instance has its own captured variable. Remember to be mindful of closures and captured variables when working with lambda expressions and anonymous methods in C#.