关于foreach语句在C#4.5中的改进

threenewbee 2012-03-25 09:11:16
加精
看这样一段代码,你觉得会输出什么呢?

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是在循环块外部被定义的。

这里涉及到一个很重要的概念,闭包,在Lambda表达式中,我们使用了外层的自由变量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());
}


VS11下载
...全文
7088 132 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
132 条回复
切换为时间正序
请发表友善的回复…
发表回复
pengpengpp 2014-06-14
  • 打赏
  • 举报
回复
学习学习!收藏了。
叫我三三 2012-04-09
  • 打赏
  • 举报
回复
有没有试过 Array.ForEach(Arr,arr=>{});得到的结果
yojinlin 2012-04-09
  • 打赏
  • 举报
回复
感謝分享。
  • 打赏
  • 举报
回复
这个改动很坑爹。
既然错了就应该一直错下去,不然改动版本的时候真是 坑爹无极限。
lanzhengpeng2 2012-04-06
  • 打赏
  • 举报
回复
[Quote=引用 20 楼 的回复:]

vs11里目标选4或者4.5,运行结果都是12345
[/Quote]
foreach是编译时刻的语法糖,跟.Net版本其实没有关系.主要是编译器的问题.

很庆幸C++11的lambd可以指定是值捕获还是引用捕获。不过,引用捕获貌似会得到更不可预知的结果。
xiaoyu821120 2012-04-05
  • 打赏
  • 举报
回复
一直都是这样的,一般都会在循环体内重新定义变量。没用过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 y = x;
actions.Add(() => y);
}
foreach (var foo in actions)
{
Console.WriteLine(foo());
}
JZ_7975 2012-04-05
  • 打赏
  • 举报
回复
我们还是3.5
a96082 2012-04-05
  • 打赏
  • 举报
回复
~~看花眼了
a96082 2012-04-05
  • 打赏
  • 举报
回复
影响不大 毕竟还没有多少使用beta的 也许有好处 也说不定就是坏处 声明变量 更要小心了
rczjp 2012-04-05
  • 打赏
  • 举报
回复
应该不是foreach的问题吧?委托的问题。
MOTA 2012-04-05
  • 打赏
  • 举报
回复
话说 哥被闭包坑过
迪迦凹凸曼 2012-04-05
  • 打赏
  • 举报
回复
下列规则适用于 Lambda 表达式中的变量范围:

捕获的变量将不会被作为垃圾回收,直至引用变量的委托超出范围为止。


在外部方法中看不到 Lambda 表达式内引入的变量。


Lambda 表达式无法从封闭方法中直接捕获 ref 或 out 参数。


Lambda 表达式中的返回语句不会导致封闭方法返回。


Lambda 表达式不能包含其目标位于所包含匿名函数主体外部或内部的 goto 语句、break 语句或 continue 语句

baiwenyu 2012-04-05
  • 打赏
  • 举报
回复
再一次加深认识
liyajie865808403 2012-04-04
  • 打赏
  • 举报
回复
长见识了
  • 打赏
  • 举报
回复
for循环同样输出5个5
 for(int x=0;x<data.Length;x++)
{
actions.Add(() => x);
}
  • 打赏
  • 举报
回复


从没发现这个问题,学习了。

不用foreach,直接这样写也行
 int[] data = new int[] { 1, 2, 3, 4, 5 };
var actions = from t in data select new { id=t };
foreach (var foo in actions)
{
Console.WriteLine(foo.id);
}
cloudtian101 2012-04-04
  • 打赏
  • 举报
回复
这种写法有问题,本来foreach就是迭代的算法,仅仅读取数据,不应该附着过多的操作。
woai18851886 2012-04-04
  • 打赏
  • 举报
回复
你们真是一群geek。 我自叹不如啊。
woai18851886 2012-04-04
  • 打赏
  • 举报
回复
你们真是一群geek。 我自叹不如啊。
gzw13999 2012-04-03
  • 打赏
  • 举报
回复
你们真是一群geek。 我自叹不如啊。
加载更多回复(67)

111,101

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧