我遇到了一个关于 C# 的有趣问题。我有如下代码。列表
我遇到了一个有关 C# 的有趣问题。我有如下代码。
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
actions.Add(() => variable * 2);
++ variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
我希望它输出 0、2、4、6、8。但是,它实际上输出了五个 10。
看起来这是因为所有操作都引用一个捕获的变量。因此,当它们被调用时,它们都有相同的输出。
有没有办法解决这个限制,让每个动作实例都有自己的捕获变量?
正如其他人所说,它与循环无关。它是 C# 中匿名函数体中的变量捕获机制的效果。当您定义 lambda 作为示例时;
actions.Add(() => variable * 2);
为 lambda 函数 <>c__DisplayClass0_0 <>c__DisplayClass0_0 () => () => variable * 2 .
在生成的类(容器)内部,它会生成一个名为 变量 ,该字段具有一个同名的捕获变量和包含 lambda 主体的方法 b__0()。
[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
public int variable;
internal int <Main>b__0()
{
return variable * 2;
}
}
然后名为变量的局部变量 成为 容器类 (<>c__DisplayClass0_0) 的一个字段
<>c__DisplayClass0_.variable = 0;
while (<>c__DisplayClass0_.variable < 5)
{
list.Add(new Func<int>(<>c__DisplayClass0_.<Main>b__0));
<>c__DisplayClass0_.variable++;
}
因此,增加变量会导致容器类的字段依次增加,并且因为我们在 while 循环的所有迭代中都获得容器类的一个实例,所以我们得到相同的输出,即 10。
您可以通过将循环体内捕获的变量重新分配给新的局部变量来防止
while (variable < 5)
{
var index = variable; // <= this line
actions.Add(() => index * 2);
++ variable;
}
顺便说一句,这种行为在 .Net 8 Preview 中仍然有效,并且我发现这种行为存在很多缺陷且具有欺骗性。