0

I understand plenty of questions have been raised and answered or discussed on C# closure. But please spare me a little time on my little experiment ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      using (var timer = new Timer(500))
      {
        timer.AutoReset = false;

        GetFunc2(timer, 0);
        // GetFunc3(timer, 0);

        timer.Start();

        Console.ReadLine();
      }
    }

    static void GetFunc2(Timer timer, int i)
    {
      for (; i < 5; ++i)
      {
        timer.Elapsed += (obj, e) =>
          {
            Console.WriteLine(i);
          };
      }
    }

    static void GetFunc3(Timer timer, int i)
    {
      timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i++);
      };

      timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i++);
      };

      timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i++);
      };

      timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i++);
      };

      timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i++);
      };
    }
  }
}

By calling GetFunc2 and GetFunc3 in the Main individually, we can see the outputs are different although GetFun3 looks merely like a simple expansion of GetFunc2. Any one knows why? I think ildasm can reveal the different generated code, but I do want to know why. Tested on VS2012 Pro, .net 4.5.

Marc B
  • 356,200
  • 43
  • 426
  • 500
W Si
  • 123
  • 1
  • 7
  • You may want to provide us with the different outputs you see, not everyone will have access to a C# compiler – Lukazoid Feb 18 '13 at 00:13
  • Please give this a more precise title. As it stands this question is about one piece of code, you will need to formulate it more generally so I am voting to close as too localized. – djechlin Feb 18 '13 at 00:28
  • @djechlin what is being asked is quite clear, and there is a general principle to that. If you can come up with a better title please feel free to edit the question. – Andrew Savinykh Feb 18 '13 at 00:31
  • @zespri I did not say the question isn't clear; I am insisting OP goes through the research effort of figuring out how to ask this question more generally. This is an important skill to develop in asking questions on SO and once the question is formulated generally it will probably be exposed as a duplicate. – djechlin Feb 18 '13 at 00:33

3 Answers3

3

The amount of times this has been covered is overwhelming..

I will not go over the discussions again, but look at Eric Lippert's answer and the others here:

https://stackoverflow.com/a/8899347/1517578

The comments on those answers are also interesting reading.

Also, the link to Eric's blog post about it is here: http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

Basically, you need to copy the variable that is closed over into a local variable:

static void GetFunc2(Timer timer, int i)
{
  for (; i < 5; ++i)
  {
    int i2 = i; // STORE IT
    timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i2); // USE THE NEWLY STORED VERSION
      };
  }
}

This results in:

0
1
2
3
4

..as expected.

Community
  • 1
  • 1
Simon Whitehead
  • 63,300
  • 9
  • 114
  • 138
0

AFAIK the closure always contains the reference to local variables of "parent" method. So the diference is that you are incrementing the value before actually calling those functions in GetFunc2. So in the time of calling, there is value of 5 already. In the GetFunc3 you are incrementing the value at time of raising the event, so it will count.

Petr Kalandra
  • 335
  • 2
  • 7
0

In case of GetFunc2 you increment i before you call (execute) the anonymous method, thus when you do call the method you see, that i is already incremented.

In case of GetFunc3 you don't increment i until each of the anonymous methods are called so it starts with zero and keep getting incremented with each call.

Andrew Savinykh
  • 25,351
  • 17
  • 103
  • 158