Anonymous Methods, Part 2 of ?

A lot of people seem to have trouble grasping how anonymous methods affect the lifetimes of locals.  SO hopefuly I can clarify that a little.  Previous to anonymous methods, the lifetime and the visibility of a local were identical for all practical purposes.  Because of the visibility restrictions, there was no way to tell if a loop was sharing the same instance of a local or using a new instance of a local.  Example:


void SomeMethod(){    int outer = 0;    for (int i = 0; i < 10; i++)    {    ``    int inner = i;        Console.WriteLine("outer = {0}", outer++);        Console.WriteLine("i = {0}", i);        Console.WriteLine("inner = {0}", ++inner);    }``}


Here we have 3 locals: outer, i, and inner.  Conceptually there is one instance of outer, and i for each invocation of SomeMethod. I think everybody would agree to that so far.  Now comes the tricky part: there is conceptually a unique instance of inner for each iteration of the loop or each entry into the containing scope.  That means that in this example there are 10 different instances of inner.  However, because none of the lifetimes overlap, there is no way to detect this, and the C# compiler uses the same slot for all instances.  The runtime might even use the same register for each instance.  As soon as you add anonymous methods to the mix, then lifetimes do have the ability to overlap, and suddenly your code can percieve the different instances.  As an example, we'll modify the above code to not write out the variables directly, but instead create 10 anonymous methods (one for each loop) that does the same thing.  We'll run each anonymous method twice: once inside the loop when it is created, and once after the loop has finished.


delegate void NoArgs();

void SomeMethod(){    NoArgs [] methods = new NoArgs[10];``    int outer = 0;    for (int i = 0; i < 10; i++)    {    ``    int inner = i;        methods[i] = delegate {            Console.WriteLine("outer = {0}", outer++);            Console.WriteLine("i = {0}", i);            Console.WriteLine("inner = {0}", ++inner);        };        methods[i]();    }``    for (int j = 0; j < methods.Length; j++)        methods[j]();}


 

Now test yourself and see if you can predict the actual output.  Just remember that the number of instances hasn't changed, only their lifetimes. The first time we run the anonymous methods, we get the exact same output as before: outer counts from 0 to 9, i counts from 0 to 9, and inner counts from 1 to 10.  Now the second time we run the anonymous methods things might be a little supprising. outer continues counting from 10 to 19, i is stuck 10, and inner looks like it is counting from 2 to 11!  If you think you know why, post a comment, or email me.  If this is so simple that I should stop wasting my time and yours, let me know.  Otherwise, I explain the whys and hows in my next few posts.

Here's the acutal output:


outer = 0i = 0inner = 1outer = 1i = 1inner = 2outer = 2i = 2inner = 3outer = 3i = 3inner = 4outer = 4i = 4inner = 5outer = 5i = 5inner = 6outer = 6i = 6inner = 7outer = 7i = 7inner = 8outer = 8i = 8inner = 9outer = 9i = 9inner = 10outer = 10i = 10inner = 2outer = 11i = 10inner = 3outer = 12i = 10inner = 4outer = 13i = 10inner = 5outer = 14i = 10inner = 6outer = 15i = 10inner = 7outer = 16i = 10inner = 8outer = 17i = 10inner = 9outer = 18i = 10inner = 10outer = 19i = 10inner = 11


--Grant