C# 4.0中foreach对闭包的影响

int[] data = new int[] { 1,2,3,4,5 };

List<Func<int>> actions = new List<Func<int>>();
foreach (int x in data)
{
actions.Add(()=>x);
}
foreach (var foo in actions)
{
Console.WriteLine(foo());
}

上面有一段代码,你觉得会输出什么?如果你使用的是C# 4.0,运行结果是55555,结果很令人吃惊,因为在C#4.0中foreach是这样运行的

int[] data = new int[] { 1, 2, 3, 4, 5 };
List<Func<int>> actions = new List<Func<int>>();
IEnumerator e = data.GetEnumerator();

int x = 0;
while (e.MoveNext())
{
x = (int)e.Current;
actions.Add(() => x);
}

foreach (var foo in actions)
{
Console.WriteLine(foo());
}

注意迭代变量x是在循环外部定义的,这里使用了一个很重要的概念:闭包,我们使用了外层的自由变量x,注意,在调用lambda表达式的时候,x会被求值,而这个定义在外部的x变量在循环终了等于5,这是为什么都是输出5的原因
我们希望他输出的结果是12345,这样就可以修改为:

int[] data = new int[] { 1, 2, 3, 4, 5 };
List<Func<int>> actions = new List<Func<int>>();
IEnumerator e = data.GetEnumerator();

while (e.MoveNext())
{
int x = 0;
x = (int)e.Current;
actions.Add(() => x);
}

foreach (var foo in actions)
{
Console.WriteLine(foo());
}

这一次,我们将x定义到块的内部。因此每当循环执行一次,都会产生一个局部变量x,闭包就会对每一个迭代单独求值,所以输出就是我们期望的12345了。
因为这个问题,在C# 4.0时代,我们必须非常小心foreach对闭包的影响,在C# 4.5(VS11 Beta)中,编译器终于做出了改变。
回到开头的代码,在VS11 Beta中会产生12345的输出了。最后说一下,如果你希望编写出C# 4.0和C# 4.5编译完全一致的代码,你可以这么写:

int[] data = new int[] { 1, 2, 3, 4, 5 };
List<Func<int>> actions = new List<Func<int>>();
foreach (int x in data)
{
int x1 = x;
actions.Add(() => x1);
}
foreach (var foo in actions)
{
Console.WriteLine(foo());
}



posted @ 2012-04-05 16:51  Lordbaby  阅读(729)  评论(0)    收藏  举报