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 (); |
输出:
为什么在执行操作时总是选择strings中的最后一个元素?我如何才能达到期望的输出,即:
您的操作是一个闭包,因此它访问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 ); }));
} |
- 啊,你赢了。我知道怎么修,但我不记得为什么。关闭!我需要结束!+ 1:
- @乔舒亚,不久前,我学到了更深一点的东西。这可能有助于进一步阅读stackoverflow.com/questions/9412672/&hellip;
- 有趣的是,我从未意识到。谢谢。
此行为由闭包限制。
lambda中存在的变量是引用而不是值副本。这意味着它指向了str假定的最新值,在您的例子中是"ghi"。这就是为什么每次调用它都只转到相同的内存位置并恢复到相同的值的原因。
如果您编写代码,就像在提供的答案中一样,您每次都强制C#编译器重新生成一个新值,因此新地址将传递给labmda,因此每个lambda都有自己的变量。
顺便说一句,如果我没弄错的话,C#小组承诺在C# 5.0中修复这种非自然行为。因此,最好查看他们关于这个主题的博客,以获取未来的更新。
- +一个很好的解释。值得一提的是,Java是用另一种方式来做的。如果处理Runnable(通常)以启动线程,那么变量将被复制到新的上下文中。这也是为什么Java强迫你使它们成为EDOCX1,1的原因。
这是一个相当复杂的情况。简短的答案是在将局部变量分配给闭包之前创建该变量的副本:
1 2
| string copy = str ;
actions .Add(new Action (() => { Trace .WriteLine(copy ); })); |
有关闭包的更多信息,请参阅本文。