一尘不染

在C#中的循环中捕获的变量

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。但是,它实际上输出5个10s。

似乎这是由于所有操作都引用了一个捕获的变量。结果,当它们被调用时,它们都具有相同的输出。

有没有办法解决这个限制,让每个动作实例都有自己的捕获变量?


阅读 230

收藏
2020-05-19

共1个答案

一尘不染

是的-在循环中获取变量的副本:

while (variable < 5)
{
    int copy = variable;
    actions.Add(() => copy * 2);
    ++ variable;
}

您可以认为它就像C#编译器每次击中变量声明时都创建一个“新”局部变量一样。实际上,它将创建适当的新闭包对象,并且如果您在多个作用域中引用变量,它会变得复杂(就实现而言),但是它是可行的:)

请注意,此问题更常见的情况是使用forforeach

for (int i=0; i < 10; i++) // Just one variable
foreach (string x in foo) // And again, despite how it reads out loud

有关此操作的更多详细信息,请参见C#3.0规范的7.14.4.2节,我有关闭包的文章也提供了更多示例。

请注意,从C#5编译器及更高版本开始(即使指定了C#的早期版本),其行为也已foreach更改,因此您不再需要进行本地复制。有关更多详细信息,请参见此答案

2020-05-19