user11224591
user11224591

Reputation:

Local variables captured by a lambda expression?

I'm reading a book which says:

Local variables captured by a lambda expression or anonymous delegate are converted by the compiler into fields, and so can also be shared:

class ThreadTest
{
    static void Main()
    {
       bool done = false;
       ThreadStart action = () =>
       {
           if (!done) { done = true; Console.WriteLine ("Done"); }
       };
       new Thread (action).Start();
       action();
    }

    /*
    void TestField() 
    {
       bool b = done; //error cannot reference `done`
    }
    */
}

but if the compiler has converted done into a field, then why I am unable to access it in another method TestField?

Upvotes: 4

Views: 802

Answers (3)

Christos
Christos

Reputation: 53958

If you use a tool like this one:

You will notice that when your code is compiled a compiler generated class is generated and the done is declared as a field of this class:

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
    public bool done;

    internal void <Main>b__0()
    {
        if (!done)
        {
            done = true;
            Console.WriteLine("Done");
        }
    }
}

So you can't access that field via your code. The done is not defined as a class field of ThreadTest class but as a field in the compiler generated class. Later on, it is used in your Main method as below:

private static void Main()
{
    <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
    <>c__DisplayClass0_.done = false;
    ThreadStart threadStart = new ThreadStart(<>c__DisplayClass0_.<Main>b__0);
    new Thread(threadStart).Start();
    threadStart();
}

Upvotes: 4

bfren
bfren

Reputation: 493

No question is dumb, except the question that is not asked. Some of the other answers give more technical information, but as you say you are new to C#, I thought a simpler explanation may be helpful.

In C#, variable scope is (effectively) defined by curly braces.

In your code, done is defined in the Main() method, so it's available to everything inside that method, including lambda functions like action.

If you defined another variable x inside the action lambda, it would be available to everything inside that lambda, but not Main().

The same is true for conditional statements. If you declare a variable inside an if block, it is available to everything inside that block, but not the calling method.

This is why done is not available to TestField(), because TestField() is not defined inside Main().

Upvotes: 1

V0ldek
V0ldek

Reputation: 10563

That is some unfortunate wording. Forget about that sentence for a second and let's talk about scopes.

Simplifying, we can assume that a local variable is in scope in the entire block that encloses it.

void Foo()
{
  var x; // x is in scope now

  if (...)
  {
      var y; // y is in scope now
      
      ...
  } // y exits scope

} // x exits scope

This is not how it works exactly, but it's a useful model. In the above code, referring to y outside of the if block is an error, since there is no y in scope.

This should be enough to explain why your code doesn't work. The done variable is in scope for the entirety of the Main method, but it's not in scope in TestField.

The author of the statement is most likely trying to convey the semantics of closures. Anonymous functions create closures and can capture variables from the enclosing scope. What that means is that for example this code:

void Foo()
{
    var x = 0;

    Action inc = () => { x += 1; };
    Action print = () => { Console.WriteLine(x); };

    print();
    inc();
    print();
    Console.WriteLine(x);
}

will print

0
1
1

The local variable x is captured by both lambdas assigned to inc and print and is shared between them. The fun part of this is that captured variables can "escape scope":

static (Action inc, Action print) Foo()
{
    var x = 0;
    Action inc = () => { x += 1; };
    Action print = () => { Console.WriteLine(x); };

    return (inc, print);
}

static void Main()
{
    var (inc, print) = Foo();

    inc();
    inc();
    print(); // Prints 2.
}

Here the x variable lives longer than its scope, since it has to be accessible from the lambda functions even after the Foo method returns.

To solve the puzzle of the "field" part of your quote we must ask ourselves how the compiler achieves that. Here's how:

private sealed class <>c__DisplayClass0_0
{
    public int x;

    internal void <>b__2()
    {
        x++;
    }

    internal void <>b__3()
    {
       Console.WriteLine(x);
    }
}

static (Action inc, Action print) Foo()
{
    <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
    <>c__DisplayClass0_.x = 0;

    Action inc = new Action(<>c__DisplayClass0_.<>b__2);
    Action print = new Action(<>c__DisplayClass0_.<>b__3);
    return (item, item2);
}

static void Main()
{
    (inc, print) = Foo();
    inc();
    inc();
    print();
}

This is more-less what the compiler generates. The <>c__DisplayClass0_ is a class generated for the anonymous functions. Its name is not expressible in valid C#. As you can see, the local variable is converted into a field in the class, anonymous functions become methods of that class and they both reference the same shared field.

So you cannot normally access that field from different scopes (thank goodness you can't, that would enable some real nasty spaghetti!), unless you start invoking reflection and other dark magic.

Upvotes: 0

Related Questions