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?
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();
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With