Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How|Where are closed-over variables stored?

This is a question based on the article "Closing over the loop variable considered harmful" by Eric Lippert.
It is a good read, Eric explains why after this piece of code all funcs will return the last value in v:

 var funcs = new List<Func<int>>();
 foreach (var v in values)
 {
    funcs.Add(() => v);
 }

And the correct version looks like:

 foreach (var v in values)
 {
    int v2 = v;
    funcs.Add(() => v2);
 }

Now my question is how and where are those captured 'v2' variables stored. In my understanding of the stack, all those v2 variables would occupy the same piece of memory.

My first thought was boxing, each func member keeping a reference to a boxed v2. But that would not explain the first case.

like image 784
Henk Holterman Avatar asked Nov 17 '09 09:11

Henk Holterman


1 Answers

Ordinarily the variable v2 would be allocated some space on the stack at the start of the code block its found in. At the end of the code block (i.e. the end of the iteration) the stack is wound back (I'm describing the logical scenario not an optimised actual behaviour). Hence each v2 is in effect a different v2 from the previous iteration although its true that it would end up occupying the same memory location.

However the compiler spots that v2 is being used by an anonymous function created by the lambda. What the compiler does is hoist the v2 variable. The compiler creates a new class that has an Int32 field to hold the value of v2, it is not allocated a place on the stack. It also makes the anonymous function a method of this new type. (For simplicity I'll give this un-named class a name, lets call it "Thing").

Now in each iteration a new instance of "Thing" is created and when v2 is assigned its the Int32 field which is actually assigned not just a point in stack memory. The anonymous function expression (the lambda) will now return a delegate which has non-null instance object reference, this reference will be to the current instance of "Thing".

When the delegate for anonymous function is invoked it will execute as an instance method of a "Thing" instance. Hence v2 is available as a member field and will have the value give it during the iteration this instance of "Thing" was created.

like image 144
AnthonyWJones Avatar answered Sep 21 '22 04:09

AnthonyWJones