13

I can't understand how to loop through an Action list. When I try it, I end up with the values being the same as the previous iteration.

Here's the code (simplified example):

string[] strings = { "abc", "def", "ghi" };

var actions = new List<Action>();
foreach (string str in strings)
    actions.Add(new Action(() => { Trace.WriteLine(str); }));

foreach (var action in actions)
    action();

Output:

ghi
ghi
ghi

Why is it always selecting the final element in strings when it performs the action?
And how can I achieve the desired output which would be:

abc
def
ghi
demoncodemonkey
  • 11,730
  • 10
  • 61
  • 103

3 Answers3

19

Your action is a closure, therefore it accesses str itself, not a copy of str:

foreach (string str in strings)
{
    var copy = str; // this will do the job
    actions.Add(new Action(() => { Trace.WriteLine(copy); }));
}
Matthias Meid
  • 12,455
  • 7
  • 45
  • 79
  • Gah, you win. I knew how to fix it, but I couldn't remember the reason why. Closure! I need closure! +1 :) – Joshua Mar 05 '12 at 15:37
  • 1
    @Joshua it wasn't too long ago when I learnt in a little deeper :) ... this may be good for further reading http://stackoverflow.com/questions/9412672/lambda-expressions-with-multithreading-in-c-sharp – Matthias Meid Mar 05 '12 at 15:39
  • Interesting, I never realised. Thanks. – demoncodemonkey Mar 05 '12 at 15:46
6

This behaviour is conditionize by Closures.

The variable that is present in your lambda is a reference and not value copy. That means that points to the latest value assumed by str, which is "ghi" in your case. That's why for every call it just goes to the same memory location and recovers, naturally, same value.

If you write the code, like in answers provided, you force a C# compiler to regenerate a new value each time, so a new address will be passed to the labmda, so every lambda will have it's own variable.

By the way, if I'm not mistake, C# team promise to fix this non natural behaviour in C# 5.0. So it's better to check their blog on this subject for future updates.

Tigran
  • 61,654
  • 8
  • 86
  • 123
  • 3
    +1 good explanation. It may be worth mentioning that Java does it the other way. If you deal with `Runnable` (typically) to start a thread, the variables are *copied* in to the new context. This is also why Java forces you to make them `final`. – Matthias Meid Mar 05 '12 at 15:53
3

This is a pretty complicated situation. The short answer is to create a copy of the local variable before assigning it to the closure:

string copy = str;
actions.Add(new Action(() => { Trace.WriteLine(copy); }));

Check out this article on closures for more information.

Bas
  • 26,772
  • 8
  • 53
  • 86