1

I have the following code...

namespace ConsoleApplication2
{
    public delegate void Task();

    class Scheduler
    {
        private List<Task> tasks = new List<Task>();

        public void AddTask(Task myTask)
        {
            tasks.Add(myTask);
        }

        public void Start()
        {
            foreach (var task in tasks)
                task();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {            
            var list = new List<string>() { "A", "B", "C" };
            var scheduler = new Scheduler();
            foreach (var item in list)
            {
                scheduler.AddTask(() => { Console.WriteLine(item); });
            }
            scheduler.Start();
        }
    }
}

The output is...

C
C
C

However, if I change the foreach part of the Main method to this:

    foreach (var item in list)
    {
        var i = item;
        scheduler.AddTask(() => { Console.WriteLine(i); });
    }

I get the following output:

A
B
C

My noobish assumption was that both programs should generate the same output. Can someone please explain why it's behaving that way?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
ChrisB
  • 877
  • 2
  • 8
  • 16
  • 9
    Read Eric Lippert's blog post about this: http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx – John Koerner Sep 25 '13 at 16:51
  • 6
    Note that this is "fixed" in C# 5, and will no longer occur. You'll get the 2nd output from either option. – Reed Copsey Sep 25 '13 at 16:54
  • This is not only true for lambdas but delegate closures in general. – YK1 Sep 25 '13 at 16:56
  • @ReedCopsey this is fixed only for `foreach` loops, for other kinds of loops e.g. `for(int i = 0; i < 10; i++)` you still need to use temporary variables. – csharpfolk Sep 25 '13 at 17:03
  • @csharpfolk Yes -but this specific example is fixed ;) – Reed Copsey Sep 25 '13 at 17:06

1 Answers1

1

In the first version of your loop, the value of item is changing by the time the scheduler is executing the Console.Writeline() - item is a local variable with value 'C' when the three tasks are executing.

In the second version, you're creating a reference to item and then using that in the task, so it is correctly displaying the value at that time.

If you have ReSharper installed, it'll warn you "Access to modified closure": here's their explanation as to why:

http://confluence.jetbrains.com/display/ReSharper/Access+to+modified+closure

Allan Elder
  • 4,052
  • 17
  • 19