How can lambda expressions handle stack variables?

4

I'm learning C # and I'm reading about lambdas expressions. The question that remained is: how does this work and why does it work?

    public delegate void Test();

    public void Foobar(ref Test del)
    {
        int var = 10;
        del = () => Console.WriteLine(var);
    }

Call:

Test bar;

Foobar(ref bar);
bar();

From what I've read, the expression lambda does not harbor values but rather references. But if the var variable is a variable that is created in the stack rather than in the heap, how can this print 10, since the var variable was deallocated at the end of the Foobar ? Or did I get the concept wrong? Please explain.

    
asked by anonymous 12.06.2018 / 18:49

1 answer

7

Simple, not putting stack .

The delegate infrastructure which is the basic mechanism of lambda allows the use of what is called closure ). That is, it holds a state within the function and takes it with it wherever it goes. It is not that the data needs to become a reference, but it will need to have some reference to some object in the heap where it has the data.

Then the compiler generates an internal code that treats what would normally be in the stack and puts it in heap which is a place where the data has indefinite life time

In this case, the lifetime of this data, accessed by a variable, is linked to the lifetime of the anonymous function. In the case of .NET your destruction will occur sometime after a garbage collection is fired at the generation it is in.

So long as the lambda exists the variable will exist, it will not be freed.

Note that there may be some unexpected effect, especially in loops, since the local value may be decoupled from the lambda value. A common example is a loop going up to 10 and having lambda run later, even though the person does not realize what it is after, and the value will always be 10 instead of 1 to 10 as the person would expect. The lambda is a mechanism used for lazy evaluation .

How is it internally

I could not name anyone other than the guy who wrote the compiler code that does this, ladies and gentlemen : Eric Lippert .

class C1 {
    Func<int, int, int> M() => (x, y) => x + y;
}

Turns into:

class C1 {
    static Func<int, int, int> theFunction;
    static int Anonymous(int x, int y) => x + y;
    Func<int, int, int> M() {
        if (C1.theFunction == null) C1.theFunction = C1.Anonymous;
        return C1.theFunction;
    }
}

Actual code in SharpLab .

class C2 {
    static int counter = 0;
    int x = counter++;
    Func<int, int> M() => y => this.x + y;
}

Turns into:

class C2 {
    static int counter = 0;
    int x = counter++;
    int Anonymous(int y) => this.x + y;
    Func<int, int> M() => this.Anonymous;
}

real code in SharpLab .

class C3 {
    static int counter = 0;
    int x = counter++;
    Func<int> M(int y) => () => x + y;
}

Turns into:

class C3 {
    class Locals {
      public C3 __this;
      public int __y;
      public int Anonymous() => this.__this.x + this.__y;
    }
    Func<int> M(int y) {
      var locals = new Locals();
      locals.__this = this;
      locals.__y = y;
      return locals.Anonymous;
    }
}

real code in SharpLab .

If I fix the time I do the C4: P

    
12.06.2018 / 19:10