传统循环与LINQ -不同的输出

本文关键字:输出 循环 LINQ 传统 | 更新日期: 2023-09-27 18:16:25

在下面的c#代码中,为什么第一组打印产生的输出是

C
C
C

,但是对应的LINQ会输出


B
C

我理解第一组输出-它在退出循环时取最后一个值,但在我看来,传统循环和LINQ等价物之间应该有一致性?-在两种情况下都应该打印CCC或ABC ?

public static void Main(string[] str)
    {
        List<string> names = new List<string>() {"A", "B", "C"};
        List<Action> actions = new List<Action>();
        foreach(var name in names)
        {
            actions.Add(() => Console.WriteLine(name));
        }
        foreach(var action in actions)
        {
            action.Invoke();
        }
        List<Action> actionList = names.Select<string, Action>(s => () => Console.WriteLine(s)).ToList();
        foreach(var action in actionList)
        {
            action.Invoke();
        }
    }

传统循环与LINQ -不同的输出

这是因为你关闭了一个循环变量。我简直找不到比利伯特更能解释清楚的了。(谁能?)如果你仍然感到困惑,那就多花点时间思考一下,看看他博客上的评论——这些评论应该会给你一些启发。

在使用Linq时这是一个很常见的错误。几乎每个人都做到了。在c# 5.0(在Visual Studio 2012中使用)编译器中,这种行为已经改变,但如果可以的话,您仍然应该避免这样做。您可以将第一个循环重写为:

foreach(var name in names)
    {
        var currentName = name;
        actions.Add(() => Console.WriteLine(currentName));
    }

我想补充一下Dave Markle的解释。当他说这是因为"关闭一个循环变量"时,他是绝对正确的。要理解为什么会发生这种情况,您必须回到闭包如何与委托一起工作。看看下面这个没有循环的简单例子:

class Program
{
    delegate void TestDelegate();
    static void Main(string[] args)
    {
        List<string> names = new List<string>() { "A", "B", "C" };
        var name = names[0];
        TestDelegate test = () => { Console.WriteLine(name); };
        name = names[1];
        test();
        Console.ReadLine();
    }
}

这里实际打印出来的是"B"而不是"A"。原因是,当您调用test()时,指向的引用名称已经更改。

当c#编译你的代码时,它的魔法酱本质上把你的lambda表达式变成委托,就像上面的代码一样,而在底层,你的name变量只是一个引用,当name改变时,调用test()将返回一个不同的结果。在循环过程中,列表中的最后一项是name被设置为最后一项的内容,因此当最终调用该操作时,name只指向列表中的最后一项,也就是打印的内容。我希望我的解释不要太啰嗦。

想象一下,如果我们把所有东西都改成for循环,c#将会看到:

class Program
{
    static void Main(string[] args)
    {
        List<string> names = new List<string>() { "A", "B", "C" };
        List<Action> actions = new List<Action>();
        string name = names[0];
        Action test = () => Console.WriteLine(name);
        for (int i = 0; i < names.Count; i++)
        {
            actions.Add(test);
        }
        name = names[1];
        foreach (var action in actions)
        {
            action.Invoke(); // Prints "B" every time because name = names[1]
        }
        Console.ReadLine();
    }
}