我希望在C#中有一个关于Action
类型和Lambda的快速问题。这是代码:
static void Main(string[] args)
{
List<Action> actions = new List<Action>();
for (int I = 0; I < 10; I++)
actions.Add(new Action(() => Print(I.ToString())));
foreach (Action a in actions)
{
a.Invoke();
}
actions.Clear();
int X;
for (X = 0; X < 10; X++)
{
int V = X;
actions.Add(new Action(() => Print(V.ToString())));
}
foreach (Action a in actions)
{
a.Invoke();
}
Console.ReadLine();
}
public static void Print(string s)
{
Console.WriteLine(s);
}
如果您运行此代码,您将看到它连续十次输出10次,然后第二次输出数字0-9。它显然与我使用X vs I的方式有关,以及我如何在第二个循环中每次给我的动作一个新变量V ...可能每个新V都是内存中的新地址,但我是努力理解为什么I.ToString()在第一个循环中没有做同样的事情...为什么第一个Action中使用的I.ToString()与第二个例子的工作方式不同?
答案 0 :(得分:5)
编译器有效地扩展了for
循环:
{
int I;
for (I = 0; I < 10; I++)
{
actions.Add(new Action(() => Print(I.ToString())));
}
}
这意味着所有lambda实例都捕获I
的同一个实例,当循环退出时它将为10。
在第二个示例中,将值复制到范围为for
语句主体的变量中,并且lambda捕获此本地。每次重复循环都会有一个独特的局部。
重要的是要意识到你没有捕获变量的值,而是捕获变量本身。这就是为什么第一个例子不起作用,但第二个例子不起作用。
答案 1 :(得分:2)
这是因为它只是一个委托,当你实际调用它时它会被执行,并且在调用它时,所有动作都具有为i
设置的最后一个值,而在foreach循环的情况下它会生成值的本地副本,因此每个操作都有自己的值,使其打印为0-9。
在第一个示例中,当您在foreach循环中第一次调用委托时,i
值得到评估,此时i
中有 10 ,在第二个示例中您将值存储在本地,该局部模仿foreach循环所执行的相同行为,因为foreach循环也会生成值的本地副本。
您还可以阅读this explanation of Jon Skeet,并在那里链接到2个eric lippert的帖子,这些帖子可以为您提供更多帮助。
答案 2 :(得分:1)
在第一个循环中创建Action
时,真正保存的是调用Print()
方法,并且在调用之前不会获得变量I
的值转到Invoke()
方法,但是在循环结束并且变量I
的值为10之后会发生这种情况。
在第二种情况下,您将在每次迭代时创建一个新变量V
,这样当您执行操作时,每个操作都会调用其中创建的变量V
的值。迭代。