关于c#:循环操作列表

Looping through a list of Actions

我不明白如何循环遍历Action列表。当我尝试它时,最终得到的值与前一个迭代相同。

下面是代码(简化示例):

1
2
3
4
5
6
7
8
string[] strings = {"abc","def","ghi" };

var actions = new List<Action>();
foreach (string str in strings)
    actions.Add(new Action(() => { Trace.WriteLine(str); }));

foreach (var action in actions)
    action();

输出:

1
2
3
ghi
ghi
ghi

为什么在执行操作时总是选择strings中的最后一个元素?我如何才能达到期望的输出,即:

1
2
3
abc
def
ghi

您的操作是一个闭包,因此它访问str本身,而不是str的副本:

1
2
3
4
5
foreach (string str in strings)
{
    var copy = str; // this will do the job
    actions.Add(new Action(() => { Trace.WriteLine(copy); }));
}


此行为由闭包限制。

lambda中存在的变量是引用而不是值副本。这意味着它指向了str假定的最新值,在您的例子中是"ghi"。这就是为什么每次调用它都只转到相同的内存位置并恢复到相同的值的原因。

如果您编写代码,就像在提供的答案中一样,您每次都强制C#编译器重新生成一个新值,因此新地址将传递给labmda,因此每个lambda都有自己的变量。

顺便说一句,如果我没弄错的话,C#小组承诺在C# 5.0中修复这种非自然行为。因此,最好查看他们关于这个主题的博客,以获取未来的更新。


这是一个相当复杂的情况。简短的答案是在将局部变量分配给闭包之前创建该变量的副本:

1
2
string copy = str;
actions.Add(new Action(() => { Trace.WriteLine(copy); }));

有关闭包的更多信息,请参阅本文。