How can Stack expressions handle stack variables?

Asked

Viewed 92 times

4

I’m learning C# and I’m reading about Brazilian expressions. The question 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);
    }

Calling for:

Test bar;

Foobar(ref bar);
bar();

From what I read, the lambda expression does not contain values but references. But if the variable var is a variable created in the stack and not in the heap, as it can print 10, since the variable 'var' was offset at the end of the method Foobar? Or did I get the concept wrong? Please explain to me.

1 answer

7


Simple, not putting in the stack.

The delegate infrastructure that is the basic mechanism of lambda allows the use of what is called enclosure (closure). In other words, it traps a state within the function and takes it with it wherever it goes. It is not that the precise data becomes a reference, but it will need to have some reference to some object in the heap where you have the die.

So the compiler generates an internal code that deals with what would normally be on stack and puts in the heap which is a place where data has indefinite lifespan.

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 fired in the generation in which it is.

So while the lambda exist the variable will exist, will not be released.

Note that there may be some unexpected effect, especially on ties, since the local value may be dissociated from the value of the lambda. A common example is a loop go up to 10 and have the lambda after, even if the person does not realize that it is after, and the value will always be 10 and not 1 to 10 as the person would expect. A lambda is a mechanism used to Lazy Evaluation.

How it is internally

I couldn’t name anyone less 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;
    }
}

Real code on 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 on 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 on Sharplab.

I put in the Github for future reference.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.