If you've used C# anonymous delegates or lambdas for a while (or anonymous functions in JavaScript) you will have encountered this problem. You make a delegate for each item in a list, and they all seem to wind up referring to the last item on the list. FUME!
It's just an unfortunate side effect of the way foreach works. It reuses the same loop variable each time around the loop, and so that loop variable is created outside the scope of the loop, and so all the delegates you create inside the loop are actually looking at the same variable, not their own local copies.
This demonstrates the problem, and also gives a neat solution:
static class Extensions { public static void ForEach<T>(this IEnumerable<T> on, Action<T> action) { foreach (T item in on) action(item); } } class Program { static void Main(string[] args) { // A list of actions to execute later List<Action> actions = new List<Action>(); // Numbers 0 to 9 IEnumerable<int> numbers = Enumerable.Range(0, 10); // Store an action that prints each number (WRONG!) foreach (int number in numbers) actions.Add(() => Console.WriteLine(number)); // Run the actions, we actually print 10 copies of "9" foreach (Action action in actions) action(); // So try again actions.Clear(); // Store an action that prints each number (RIGHT!) numbers.ForEach(number => actions.Add(() => Console.WriteLine(number))); // Run the actions foreach (Action action in actions) action(); } }
By using ForEach (an extension method) instead of using foreach directly, we make the problem disappear, because now each time around the loop we have a unique number variable which is properly captured by the delegates we created.
Notes:
- The ForEach extension method I define above is actually already available as a method in the List<T> class, so if you're looping over a List<T>, you don't need that extension method.
- That extension method ought to be added to the System.Linq.Enumerable class, oughtn't it?
- One situation this can't help with is yield return. If you are looping through a list of items, and then you want to yield return each item, you're out of luck with this solution.
No comments:
Post a Comment