[C# 5.0 IN A NUTSHELL,5th Edition读书笔记]Lambda表达式捕获(capture)迭代中的变量的相关问题

本文详细解释了在C#循环中捕获迭代变量时可能导致的问题,并提供了一种有效的方法来解决输出结果不一致的情况。通过实例分析,展示了如何在循环内部使用本地变量来避免闭包作用域的问题,从而确保每次迭代时都能正确地访问到期望的值。
当你在一个循环语句中捕获迭代变量时,C#会把这个变量当成是在循环外部声明的。也就是说在每次迭代中捕获的变量值都是相同的。

下面程序的输出结果是333而不是012:

Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
actions [i] = () => Console.Write (i);  // 此行为粗体字
foreach (Action a in actions) a(); // 333

每一个闭包(粗体字部分)都捕获了相同的变量i(这样做是符合情理的,只要你把i看成是在每次循环迭代之间都保存其数值的变量,如果你愿意,你甚至可以在循环之间显式地改变i的数值)。因此委托被调用的时候,每个看到的i的值都是调用那一时刻i的数值,也就是3.我们可以把前面的循环展开以便更好地说明情况:


Action[] actions = new Action[3];
int i = 0;
actions[0] = () => Console.Write (i);
i = 1;
actions[1] = () => Console.Write (i);
i = 2;
actions[2] = () => Console.Write (i);
i = 3;
foreach (Action a in actions) a(); // 333


如果我们希望程序输出012,可以采用下面的解决方案:将迭代变量赋给一个本地变量,这个本地变量的作用域被限制在循环内部:
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
int loopScopedi = i;
actions [i] = () => Console.Write (loopScopedi);
}
foreach (Action a in actions) a(); // 012

这个方案使得闭包在每次循环都捕捉一个不同的数值。


在C# 5.0之前,foreach循环也遵循同样的原则

Action[] actions = new Action[3];
int i = 0;
foreach (char c in "abc")

actions [i++] = () => Console.Write (c);

foreach (Action a in actions) a(); // ccc in C# 4.0

这造成了很大的困扰:与for循环不同,foreach循环中的迭代变量是不可变的(immutable),因此通常都会将其作为一个循环内部的本地变量看待。好消息是这个问题在C# 5.0里已经被解决了,上面的例子在C# 5.0中得到的输出结果是"abc".

 

从学术上讲,这可以被称为一个破坏性变化(breaking change),因为在C# 5.0中重新编译一段C# 4.0代码会得到不同的结果。通常情况下,C#团队会尽量避免破坏性变化;但是,对于当前这个例子,这个破坏(break)几乎可以肯定意味着原来的C# 4.0程序有一个没有检测到的缺陷(bug),而不是基于对老版本的行为的透彻了解而刻意为之。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值