本周我参加了荷兰的TechDays 2013,我得到了一个有趣的测验问题.问题是:以下程序的输出是什么.这是代码的样子.
class Program
{
delegate void Writer();
static void Main(string[] args)
{
var writers = new List<Writer>();
for (int i = 0; i < 10; i++)
{
writers.Add(delegate { Console.WriteLine(i); });
}
foreach (Writer writer in writers)
{
writer();
}
}
}
Run Code Online (Sandbox Code Playgroud)
显然,我给出的答案是错误的.我认为,因为int是一个值类型,传递的实际值Console.WriteLine()被复制,所以输出将是0 ... 9.但是i在这种情况下作为参考类型处理.正确答案是它会显示十次10.有人可以解释为什么以及如何解释?
我认为,因为int是一个值类型,所以传递给Console.WriteLine()的实际值会被复制
这是完全正确的. 当您调用WriteLine该值时将被复制.
所以,你WriteLine什么时候打电话?它不在for循环中.你不是在那个时候写任何东西,你只是在创建一个代表.
foreach在调用委托时,直到循环时,才会将变量中的值i复制到堆栈以进行调用WriteLine.
那么,循环i期间的价值是foreach什么?对于循环的每次迭代,它是10 foreach.
所以现在你要问," 好吧,i在这期间有什么foreach loop, isn't it out of scope.嗯,不,它不是.这证明是一个"闭包".当一个匿名方法引用变量的范围需要持续的变量时,匿名方法,可以是任何时间段.如果没有什么特别的事情可以完成,那么变量将是随机垃圾,包含任何碰巧被困在内存中的位置.C#主动确保情况不会发生.
那它是做什么的?它创建了一个闭包类; 它是一个包含许多字段的类,代表所有被关闭的字段.换句话说,代码将被重构为如下所示:
public class ClosureClass
{
public int i;
public void DoStuff()
{
Console.WriteLine(i);
}
}
class Program
{
delegate void Writer();
static void Main(string[] args)
{
var writers = new List<Writer>();
ClosureClass closure = new ClosureClass();
for (closure.i = 0; closure.i < 10; closure.i++)
{
writers.Add(closure.DoStuff);
}
foreach (Writer writer in writers)
{
writer();
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们都有一个匿名方法的名称(所有匿名方法都由编译器给出一个名称),我们可以确保变量将存在,只要引用匿名函数的委托存在.
看看这个重构器,我希望很清楚为什么结果会10被打印10次.