传统循环与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时这是一个很常见的错误。几乎每个人都做到了。在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();
}
}