C# lambda表达式捕获的局部变量?
我正在读一本书,上面写着: lambda表达式或匿名委托捕获的局部变量由编译器转换为字段,因此也可以共享: 但是如果编译器已将C# lambda表达式捕获的局部变量?,c#,C#,我正在读一本书,上面写着: lambda表达式或匿名委托捕获的局部变量由编译器转换为字段,因此也可以共享: 但是如果编译器已将done转换为一个字段,那么如果您使用类似的工具,为什么我无法在另一个方法TestField?中访问它: 您会注意到,在编译代码时,会生成编译器生成的类,并且done被声明为该类的字段: [CompilerGenerated] private sealed class <>c__DisplayClass0_0 { public bool done;
done
转换为一个字段,那么如果您使用类似的工具,为什么我无法在另一个方法TestField
?中访问它:
您会注意到,在编译代码时,会生成编译器生成的类,并且done
被声明为该类的字段:
[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
public bool done;
internal void <Main>b__0()
{
if (!done)
{
done = true;
Console.WriteLine("Done");
}
}
}
这是一些令人遗憾的措辞。先忘掉那句话,让我们谈谈范围 简化一下,我们可以假设局部变量在包含它的整个块的作用域中
void Foo()
{
var x; // x is in scope now
if (...)
{
var y; // y is in scope now
...
} // y exits scope
} // x exits scope
这不是它的工作原理,但它是一个有用的模型。在上述代码中,由于范围内没有y
,因此如果块是错误,则在块外引用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捕获,并在它们之间共享。有趣的是,捕获的变量可以“转义范围”:
在这里,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\uu显示类0\u0
{
公共int x;
内部空隙b__2()
{
x++;
}
内部空隙b__3()
{
控制台写入线(x);
}
}
静态(Action inc,Action print)Foo()
{
c_uuuDisplayClass0_0C_uuuuDisplayClass0_0=新的c_uuuDisplayClass0_0();
c_uuu显示类0_uuu.x=0;
Action inc=新操作(c__显示类0_uu.b__2);
动作打印=新动作(c__显示类别0_uu.b__3);
返回(项目,项目2);
}
静态void Main()
{
(inc,print)=Foo();
公司();
公司();
打印();
}
这比编译器生成的要少。c\uu显示类0\uu
是为匿名函数生成的类。其名称不能用有效的C#表示。如您所见,局部变量被转换为类中的字段,匿名函数成为该类的方法,它们都引用相同的共享字段
因此,除非你开始调用反射和其他黑魔法,否则你通常无法从不同的范围访问该字段(谢天谢地,你不能,这将启用一些真正讨厌的意大利面条!)。除了没有被问到的问题,没有问题是愚蠢的。其他一些答案提供了更多的技术信息,但正如您所说,您是C#新手,我认为一个更简单的解释可能会有所帮助
在C#中,变量范围(有效地)由大括号定义
在您的代码中,done
是在Main()
方法中定义的,因此该方法中的所有函数都可以使用它,包括像action
这样的lambda函数
如果在action
lambda中定义了另一个变量x
,则该变量可用于该lambda中的所有变量,但不适用于Main()
条件语句也是如此。如果在If
块内声明变量,则该块内的所有对象都可以使用该变量,但调用方法不能使用该变量
这就是为什么done
对TestField()
不可用的原因,因为TestField()
没有在Main()
中定义,我想是因为done
在Action
委托的范围内(其行为与函数参数中的“正常”相同)该字段不是为ThreadTest类生成的,而是为lambda函数的类容器生成的,如果Main()
不是static
,会发生什么?
0
1
1
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.
}
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();
}