c# – How can lambda expressions handle stack variables?

Question:

I'm learning C# and reading about lambda expressions. The question that remains is: how does it 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 read, the lambda expression does not hold values ​​but references. But if the var variable is a variable created on the stack and not on the heap, how does this manage to print 10 since the 'var' variable was deallocated at the end of the Foobar method? Or did I get the concept wrong? Please explain.

Answer:

Simple, not putting it on the stack .

The delegate infrastructure that is the basic mechanism of lambda allows the use of what is called closure . That is, it locks a state inside the function and takes it with it wherever it goes. It's not that the data needs to be transformed into a reference, but it needs to have some reference to some object on the heap where the data is.

Then the compiler generates an internal code that handles what would normally be on the stack and puts it on the heap , which is a place where the data has an indefinite lifetime.

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 its destruction will occur sometime after a garbage collection is triggered in the generation it is in.

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

Note that there may be some unexpected effect, especially in loops, as the local value may be decoupled from the lambda value. A common example is a loop going to 10 and having the lambda run later, even if the person doesn't realize it is later, and the value will always be 10 and not from 1 to 10 as one would expect. Lambda is a mechanism used for lazy evaluation .

how is it internally

I couldn't 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;
}

Transforms 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;
    }
}

Real code not SharpLab .

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

Transforms 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 not SharpLab .

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

Transforms 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 .

I put it on GitHub for future reference .

Scroll to Top