在下面的C#代码中,为什么第一组打印产生
的输出Ç
ç
C
但LINQ等效的产生
的输出一个
乙
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();
}
}
答案 0 :(得分:7)
这是因为你是closing over a loop variable。我根本无法解释它比Lippert更好。 (任何人都可以吗?)如果你仍然感到困惑,请花点时间思考一下并阅读他博客上的评论 - 他们应该具有启发性。
使用Linq时这是一个非常常见的错误。几乎每个人都做到了。在C#5.0(在Visual Studio 2012中使用)编译器中,此行为已更改,但如果您可以帮助它,仍应避免执行此操作。您可以将第一个循环重写为:
foreach(var name in names)
{
var currentName = name;
actions.Add(() => Console.WriteLine(currentName));
}
问题就会消失。
答案 1 :(得分:4)
我想补充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()的调用将返回不同的结果。当您循环播放时,列表中的最后一项是设置为最后的名称,因此当最终调用该操作时,名称仅指向列表中的最后一项,即打印的内容。我希望我的解释不是太冗长。
想象一下,当我们将所有内容都更改为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();
}
}