Captured variable in a loop in C#
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.
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.
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.