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();
    }
}

输出结果

 

 

闭包陷阱解释:

  1. 在for循环中,只能有一个 i 变量。即在第一次循环时,i 的地址就分配好了(注意了,这里i的地址第一次分配后是不变的),不会因为循环次数的多少而发生任何改变,其改变的只能是里面装载的值。
  2. 当使用匿名方法时传进去的是变量的地址,而不是具体值。只有当真正执行这个匿名方法时,才会去确定它的值。这就是为什么上面的例子中,其结果均为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);
    }
}

如何避免闭包陷阱

  1. 就是在for循环中,定义一临时变量tmpNum,存储i的值即可。因为编译器会对该临时变量重新分配内存,这样,每次循环,都重新分配新的内存,就不会有这个问题了。
  2. 使用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);


posted @ 2021-09-29 12:30  小林野夫  阅读(3224)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/