in

 

Chad Myers' Blog

Department of Problem Prevention

Interesting (good) Behavior of Closures

For some reason, I didn't think this would work, but it does:

            Customer c = null;
            Func<string> func = ()=>c.LookupName;
            c = new Customer {LookupName = "First"};
            System.Diagnostics.Debug.WriteLine(func());

I would've expected an NRE in the WriteLine because the 'c' reference (null) would've been packaged up by the closure.  But apparently it packages up the reference to the reference also so that if the 'c' variable value changes, so does the closure's reference to it. 

The output is not an NRE, but rather "First".

I was curious what it would do with valid references. Consider the following example:

            Customer c = new Customer{LookupName="First"};
            Func<string> func = ()=>c.LookupName;
            c = new Customer {LookupName = "Second"};
            System.Diagnostics.Debug.WriteLine(func());

The output is, as you would expect, "Second".

One last stretch here, what about stack-based value types:

            int i = -1;
            Func<int> func = ()=>i;
            i = 99;
            System.Diagnostics.Debug.WriteLine(func());

The output is 99.

The way it works under the hood is that the compiler doesn't actually create a new stack variable called 'i' (like it would normally), it creates a new class called <>c__DisplayClass2d in my case. Well, that's hard to type, so let's just call it FancyClass:

            public class FancyClass{
                public int i;

                public int GetI(){
                    return i;
                }
            }

Then, it re-writes -- rather it compiles slightly different IL -- the code above (the int i = -1 example) like this:

            FancyClass c = new FancyClass();
            c.i = -1;
            Func<int> func = ()=>c.i;
            c.i = 99;
            System.Diagnostics.Debug.WriteLine(func());

Seen like this, it seems a bit more obvious.

Many of you are probably saying "Duh!", but this went against my understanding of how closures package up their context. 

Published Mar 08 2008, 10:42 PM by chadmyers
Filed under: ,

Comments

 

Dew Drop - March 10, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - March 10, 2008 | Alvin Ashcraft's Morning Dew

March 10, 2008 7:01 AM
 

Jon Scolamiero said:

I like this behavior as it lets us do some setup early on in a method without worrying that the variable my not be instantiated yet.

March 11, 2008 1:33 PM
 

Simone Busoli said:

Hi Chad, yeah, that's a cool behavior, and has somewhat become intuitive, since as lambda - or an anonymous method - are pointers to methods, not actually code which gets executed, therefore they create a closure around the variables of the environment and actually use them only when they are invoked.

That's why, when you create loops which call anonymous methods, at times you need to declare an inner variable which disconnects from the outer closure and keeps the right value of the variable for each iterations, and the following example usually doesn't work as expected:

var handle = new ManualResetEvent(false);

for(int i = 0; i < 1000000; i++)

   ThreadPool.QueueUserWorkItem(() =>  

   {

       handle.WaitOne();

       Console.WriteLine(i);

   });

handle.Set();

PS. Wrote it without the compiler, I'm not sure it does, but you get the idea.

March 14, 2008 3:15 PM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About chadmyers

Chad Myers is a senior .NET software developer specializing in enterprise software designs and architectures. He has over 10 years of software development experience and a proven track record of Agile, test-driven project leadership using both Microsoft and open source tools. He is community leader who speaks at the Austin .NET User's Group, the ADNUG Code Camp, and participates in various development communities and open source projects.
Copyright Los Techies 2007. All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems