Closures and the loop variable trap
A lambda captures the variable, not the value it had at that moment. In a for loop that surprises almost everyone once.
Watch it animatedStep through it, with the running line highlighted. Free with an account.The idea
A for loop has one variable i for the whole loop. All three lambdas share it, and by the time they run the loop has finished with i = 3. Declaring a variable inside the body gives each lambda its own.
Code
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
actions.Add(() => Console.Write(i)); // captures the variable i
foreach (var act in actions) act(); // prints 333
var fixedActions = new List<Action>();
for (int i = 0; i < 3; i++) {
int copy = i; // a fresh variable each time
fixedActions.Add(() => Console.Write(copy));
}
foreach (var act in fixedActions) act(); // prints 012Tips
- A foreach loop does not have this problem: since C# 5 its variable is fresh on every iteration.
- The compiler moves captured variables into a hidden heap object so they can outlive the method.
In the .NET library
The same rule explains why a lambda passed to Task.Run sees later changes to a captured local. Copy the value to a new local first if you need a snapshot.