Reputation:
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
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
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
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