C#闭包
闭包的概念
闭包是一个代码块(在C#中,指的是匿名方法或者Lambda表达式,也就是匿名函数),并且这个代码块使用到了代码块以外的变量,于是这个代码块和用到的代码块以外的变量(上下文)被“封闭地包在一起”。当使用此代码块时,该代码块里使用的外部变量的值,是使用该代码块时的值,并不一定是创建该代码块时的值。正常的函数内部的变量是保存在 帧栈中(call stack>record Frame) 。当形成闭包以后 托管堆就形成一个类 类 用来存放该变量
一句话概括,闭包是一个包含了上下文环境的匿名函数。
直接上代码,加深理解。
static void Main(string[] args) { //匿名方法 int i = 90; TestDelegate dd = delegate { Console.WriteLine("fsdfd"); i = i + 10; }; }
我用ILSpy 打开,看看匿名函数到底是啥:
从上面的IL代码来看 匿名方法 变成了<>c类的<Main>b__0_0方法,完整签名:
闭包就是指IL代码中的<>c_DisplayClss2.0 类。编译器把匿名方法和使用到的外部变量 打包在一起封装成类<>c_DisplayClss2.0。并且生成属性来读取变量 i
形成闭包的条件:
嵌套定义的函数、匿名函数、lambda表达式、将函数作为参数或者返回值。
闭包的优点
使用闭包,我们可以轻松的访问外层函数定义的变量,例如在匿名方法中。比如有如下场景,在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”的每次循环都会是循环变量的一个拷贝,这样闭包就看起来关闭了(没有了)。
闭包应用
需求:实现变量a 自增
1、通过全局变量,可以实现,但会污染其他程序
var a = 10;
function Add(){
a++;
console.log(a);
}
Add();
Add();
Add();
2、定义一个局部变量,不污染全局,但是实现不了递增
var a = 10;
function Add2(){
var a = 10;
a++;
console.log(a);
}
Add2();
Add2();
Add2();
console.log(a);
3、通过闭包,可以是函数内部局部变量递增,不会影响全部变量,完美!!
var a = 10;
function Add3(){
var a = 10;
return function(){
a++;
return a;
};
};
var cc = Add3();
console.log(cc());
console.log(cc());
console.log(cc());
console.log(a);
编程是个人爱好