views:

1924

answers:

4

I met a 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 output five 10s.

It seems that it is due to all actions are referring to one captured variable. As a result, when they get invoked, they all have same output.

Is there any way to walk round this limit to have each action instance have its own captured variable?

+14  A: 

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.

Jon Skeet
I should know better than to try and answer questions when its daytime in the UK!
cfeduke
Jon's book also has a very good chapter on this (stop being humble, Jon!)
Marc Gravell
It looks better if I let other people plug it ;) (I confess that I do tend to vote up answers recommending it though.)
Jon Skeet
Okay I've finally ordered his book and it replaces my Erlang book in the read queue.
cfeduke
hah. my copy arrived yesterday. the pints will be on Mr. Skeet if theres ever a SO Meetup ;)
Eoin Campbell
As ever, feedback to [email protected] would be appreciated :)
Jon Skeet
+1  A: 

Yes you need to scope variable within the loop and pass it to the lambda that way:

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

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

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

Console.ReadLine();
cfeduke
+1  A: 

The way around this is to store the value you need in a proxy variable, and have that variable get captured.

I.E.

while( variable < 5 )
{
    int copy = variable;
    actions.Add( () => copy * 2 );
    ++variable;
}
tjlevine
Yeah, it works. But, why?
Morgan Cheng
See the explanation in my edited answer. I'm finding the relevant bit of the spec now.
Jon Skeet
Haha jon, I actually just read your article: http://csharpindepth.com/Articles/Chapter5/Closures.aspxYou do good work my friend.
tjlevine
@tjlevine: Thanks very much. I'll add a reference to that in my answer. I'd forgotten about it!
Jon Skeet
Also, Jon, I'd love to read about your thoughts on the various Java 7 closure proposals. I've seen you mention that you wanted to write one, but I haven't seen it.
tjlevine
@tjlevine: Okay, I promise to try to write it up by the end of the year :)
Jon Skeet
+3  A: 

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.

TheCodeJunkie