【问题标题】:Looping through a list of Actions循环遍历动作列表
【发布时间】:2012-03-23 02:00:11
【问题描述】:

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

这是代码(简化示例):

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();

输出:

ghi
ghi
ghi

为什么执行动作时总是选择strings中的最后一个元素?
以及如何实现所需的输出:

abc
def
ghi

【问题讨论】:

    标签: c# action enumeration


    【解决方案1】:

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

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

    【讨论】:

    • 呸,你赢了。我知道如何解决它,但我不记得原因。关闭!我需要关闭! +1 :)
    • @Joshua 不久前我学得更深一点:) ...这可能有助于进一步阅读stackoverflow.com/questions/9412672/…
    • 有趣,我从来没有意识到。谢谢。
    【解决方案2】:

    此行为由Closures 限定。

    您的 lambda 中存在的变量是 referencenot 值副本。这意味着它指向str 假定的最新值,在您的情况下为“ghi”。这就是为什么每次调用它都会转到相同的内存位置并自然而然地恢复相同的值。

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

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

    【讨论】:

    • +1 很好的解释。值得一提的是,Java 采用了另一种方式。如果您处理Runnable(通常)以启动线程,则变量将复制到新上下文中。这也是为什么 Java 强迫你将它们设为final
    【解决方案3】:

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

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

    Check out this article on closures 了解更多信息。

    【讨论】:

      猜你喜欢
      • 2020-03-30
      • 2013-06-22
      • 1970-01-01
      • 2021-12-16
      • 2017-03-02
      • 1970-01-01
      • 2018-10-26
      • 2013-09-04
      相关资源
      最近更新 更多