我正在读一本书,上面写着:
由lambda表达式或匿名委托捕获的局部变量由编译器转换为字段,因此也可以共享:
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`
}
*/
}
但是如果编译器已将done
转换为字段,那么为什么我无法通过另一方法TestField
访问它?
答案 0 :(得分:3)
如果您使用this之类的工具,
您会注意到,在编译代码时,会生成编译器生成的类,并且将done
声明为此类的字段:
[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
public bool done;
internal void <Main>b__0()
{
if (!done)
{
done = true;
Console.WriteLine("Done");
}
}
}
因此您无法通过您的代码访问该字段。 done
没有定义为ThreadTest
类的类字段,而是定义为编译器generate类中的字段。稍后,它会在您的Main方法中使用,如下所示:
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();
}
答案 1 :(得分:0)
这是一些不幸的措辞。忘掉那句话一秒钟,让我们谈谈范围。
为简化起见,我们可以假定局部变量在包围它的整个块中在范围内 。
void Foo()
{
var x; // x is in scope now
if (...)
{
var y; // y is in scope now
...
} // y exits scope
} // x exits scope
这不是确切的工作方式,但这是一个有用的模型。在上面的代码中,由于范围内没有y
,因此在if
块之外引用y
是错误的。
这足以说明您的代码为什么不起作用。 done
变量在整个Main
方法的范围内,但不在TestField
的范围内。
声明的作者很可能试图传达闭包的语义。匿名函数创建闭包,并且可以从封闭范围中捕获变量。这意味着例如以下代码:
void Foo()
{
var x = 0;
Action inc = () => { x += 1; };
Action print = () => { Console.WriteLine(x); };
print();
inc();
print();
Console.WriteLine(x);
}
将打印
0
1
1
局部变量x
被分配给inc
和print
的两个lambda 捕获,并在它们之间共享。有趣的部分是捕获的变量可以“转义范围”:
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.
}
此处x
变量的生存期超过其范围,因为即使在Foo
方法返回后也必须可以从lambda函数访问它。
要解决报价中“字段”部分的难题,我们必须问自己编译器如何实现这一点。方法如下:
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();
}
这几乎是编译器生成的。 <>c__DisplayClass0_
是为匿名函数生成的类。其名称无法在有效的C#中表达。如您所见,局部变量将转换为类中的字段,匿名函数成为该类的方法,并且它们都引用相同的共享字段。
因此,除非您开始调用反射和其他黑魔法,否则您通常无法从不同的范围访问该字段(谢天谢地,这将使您真正的讨厌的意大利面!)。
答案 2 :(得分:0)
除了没有被问到的问题,没有任何问题是愚蠢的。其他一些答案提供了更多的技术信息,但是正如您所说的,您是C#的新手,我认为更简单的解释可能会有所帮助。
在C#中,变量范围(有效地)由花括号定义。
在您的代码中,done
是在Main()
方法中定义的,因此该方法内 的所有内容都可以使用,包括action
之类的lambda函数。 / p>
如果您在x
lambda中定义了另一个变量action
,则该变量对所有{em> inside 的lambda都可用,但{{{ 1}}。
条件语句也是如此。如果在Main()
块内声明变量,则该块内的所有 都可以使用它,但调用方法不可用。
这就是if
对done
不可用的原因,因为{<1> inside TestField()
中没有定义TestField()
。