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?
Yes - take a copy of the variable inside the loop:
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
orforeach
: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.
This has nothing to do with loops.
This behavior is triggered because you use a lambda expression
() => variable * 2
where the outer scopedvariable
not actually defined in the lambda's inner scope.Lambda expressions (in C#3+, as well as anonymous methods in C#2) still create actual methods. Passing variables to these methods involve some dilemmas (pass by value? pass by reference? C# goes with by reference - but this opens another problem where the reference can outlive the actual variable). What C# does to resolve all these dilemmas is to create a new helper class ("closure") with fields corresponding to the local variables used in the lambda expressions, and methods corresponding to the actual lambda methods. Any changes to
variable
in your code is actually translated to change in thatClosureClass.variable
So your while loop keeps updating the
ClosureClass.variable
until it reaches 10, then you for loops executes the actions, which all operate on the sameClosureClass.variable
.To get your expected result, you need to create a separation between the loop variable, and the variable that is being closured. You can do this by introducing another variable, i.e.:
You could also move the closure to another method to create this separation:
You can implement Mult as a lambda expression (implicit closure)
or with an actual helper class:
In any case, "Closures" are NOT a concept related to loops, but rather to anonymous methods / lambda expressions use of local scoped variables - although some incautious use of loops demonstrate closures traps.
It is called the closure problem, simply use a copy variable, and it's done.
Yes you need to scope
variable
within the loop and pass it to the lambda that way: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:
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.
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.