Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is a c# lambda capturing variables

Why does the following code print 11 twice?

int i = 10;
Action fn1 = () => Console.WriteLine(i);
i = 11;
Action fn2 = () => Console.WriteLine(i);
fn1();
fn2();

Output 11 11

According to the answers in this post - How to tell a lambda function to capture a copy instead of a reference in C#? - a lambda is translated into a class with a copy of the captured variable. If that is the case shouldn't my example have printed 10 & 11?

Now, when a lambda captures by reference how does it affect the life time of the variable captured. For instance, assume that the above piece of code was in a function & the Actions' scope was global to the variable like this:

class Test
{
  Action _fn1;
  Action _fn2;

  void setActions()
  {
    int i = 10;
    _fn1 = () => Console.WriteLine(i);
    i = 11;
    _fn2 = () => Console.WriteLine(i);
  }

  static void Main()
  {
    setActions();
    _fn1();
    _fn2();
  }
}

In this scenario wouldn't the variable i have gone out of scope when the actions are invoked? So, are the actions left with a dangling pointer sort of reference?

like image 866
Social Developer Avatar asked Jun 12 '17 09:06

Social Developer


1 Answers

If that is the case shouldn't my example have printed 10 & 11?

No, because you've only got a single variable - fn1 captures the variable, not its current value. So a method like this:

static void Foo()
{
    int i = 10;
    Action fn1 = () => Console.WriteLine(i);
    i = 11;
    Action fn2 = () => Console.WriteLine(i);
    fn1();
    fn2(); 
}

is translated like this:

class Test
{
    class MainVariableHolder
    {
        public int i;
        public void fn1() => Console.WriteLine(i);
        public void fn2() => Console.WriteLine(i);
    }

    void Foo()
    {
        var holder = new MainVariableHolder();
        holder.i = 10;
        Action fn1 = holder.fn1;
        holder.i = 11;
        Action fn2 = holder.fn2;
        fn1();
        fn2();
    }
}

This answers your second point too: the variable is "hoisted" into a class, so its lifetime is effectively extended as long as the delegate is alive.

like image 112
Jon Skeet Avatar answered Oct 20 '22 20:10

Jon Skeet