闭包的概念
内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。但该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
闭包的优点
使用闭包,我们可以轻松的访问外层函数定义的变量,例如在匿名方法中。比如有如下场景,在WinForm中,我们希望当用户关闭窗体时,给用户一个提示。代码如下:
private void Form1_Load(object sender, EventArgs e)
{
string tipWords = "您将关闭当前对话框";
this.FormClosing += delegate
{
MessageBox.Show(tipWords);
};
}
若不使用匿名方法,我们就需要使用其他方式将tipWords变量的值传递给FormClosing注册的处理函数,这就增加了不必要的工作量。
闭包陷阱
例如有如下代码:
private static void Before()
{
Action[] actions = new Action[10];
for (var i = 0; i < actions.Length; i++)
{
actions[i] = () =>
{
Console.WriteLine(i);
};
}
foreach (var item in actions)
{
item();
}
}
输出结果

闭包陷阱解释:
- 在for循环中,只能有一个 i 变量。即在第一次循环时,i 的地址就分配好了(注意了,这里i的地址第一次分配后是不变的),不会因为循环次数的多少而发生任何改变,其改变的只能是里面装载的值。
- 当使用匿名方法时传进去的是变量的地址,而不是具体值。只有当真正执行这个匿名方法时,才会去确定它的值。这就是为什么上面的例子中,其结果均为10(for循环在最后,当i=9时,i又加了1)。
编译器帮我们做了什么
下面的代码是编译器帮我们生成的代码
private static void After()
{
Action[] actions = new Action[10];
var anonymous = new AnonymousClass();
for (anonymous.i = 0; anonymous.i < actions.Length; anonymous.i++)
{
actions[anonymous.i ] = anonymous.Action;
}
foreach (var item in actions)
{
item();
}
}
class AnonymousClass
{
public int i;
public void Action()
{
Console.WriteLine(this.i);
}
}
如何避免闭包陷阱
- 就是在for循环中,定义一临时变量tmpNum,存储i的值即可。因为编译器会对该临时变量重新分配内存,这样,每次循环,都重新分配新的内存,就不会有这个问题了。
- 使用foreach,c#5.0后,for和foreach在处理闭包问题上有了一些新的改变。为了适应不同的需求,微软对佛 foreach做了调整,“foreach”的遍历中定义的临时循环变量会被逻辑上限制在循环内,“foreach”的每次循环都会是循环变量的一个拷贝,这样闭包就看起来关闭了(没有了)。
网友评论