APP下载

深入理解闭包

消息来源:baojiabao.com 作者: 发布时间:2024-04-27

报价宝综合消息深入理解闭包

闭包的概念:

内层的函式可以引用包含在它外层的函式的变数,即使外层函式的执行已经终止。但该变数提供的值并非变数建立时的值,而是在父函式范围内的最终值。

要理解闭包,首先要搞懂清楚是变数的作用域和生命周期。我们以C#为例:

在C#中,变数作用域有三种,一种是属于类的,称之为field;第二属于函式的,我们通常称之为区域性变数;还有一种,也是属于函式的,不过它的作用范围更小,它只属于函式区域性的程式码片段,这种同样称之为区域性变数。 这三种变数的生命周期基本都可以用一句话来说明,每个变数都属于它所寄存的物件,即变数随着其寄存物件生而生和消亡。对应三种作用域我们可以这样说,类里面的变数是随着类的例项化而生,同时伴随着类物件的资源回收而消亡(当然这里不包括非例项化的static和const物件)。而函式(或程式码片段)的变数也随着函式(或程式码片段)呼叫开始而生,伴随函式(或程式码片段)呼叫结束而自动由GC释放,它内部变数生命周期满足先进后出的特性。在作用域以外不能对变数进行读写等操作。

作用域外试图去操作变数时,提示当前上下文不存在XXX等类似的错误提示。

那么这里有没有例外呢? 答案是有的

先来看一段程式码:

变数n实际上是属于函式T1的区域性变数,它本来生命周期应该是伴随着函式T1的呼叫结束而被释放掉的,但这里我们却在返回的委托b中仍然能呼叫它,因为T1呼叫返回的匿名委托的程式码片段中我们用到了n,而在编译器看来,这些都是合法的,因为返回的委托b和函式T1存在上下文关系,也就是说匿名委托b是允许使用它所在的函式或者类里面的区域性变数的,于是编译器通过一系列动作(具体动作我们后面再说)使b中呼叫的函式T1的区域性变数自动闭合,从而使该区域性变数满足新的作用范围。

闭包的优点:

使用闭包,我们可以轻松的访问外层函式定义的变数,这在匿名方法中普遍使用。比如有如下场景,在winform应用程序中,我们希望做这么一个效果,当用户关闭窗体时,给使用者一个提示框。我们将新增如下程式码:

如果我们不使用匿名函式,就必须用其他方式来把tipWords的值传递给FormClosing注册的处理函式,这就增加了不必要的程式码工作量。所以说闭包可以极大的简化我们的程式码工作量,使我们的程式码更加优美简洁。

闭包的陷阱:

应用闭包,我们要注意一个陷阱。比如有一个学生资讯的阵列,我们需要遍历每一个使用者,对各个使用者做处理后输出使用者名称。

首先建立一个学生类,包含学生姓名和年龄

然后在主函式里宣告一组学生阵列,当然我是在winform里面的按钮click事件注册的函式里写的,你也可以在别的地方。

预想的输出应该为:”张三”,”李四”,”王五”。

但是实际执行中会报错:提示索引超出界限。

为什么没有达到我们预期的效果呢?让我们再来看一下闭包的概念。内层函式引用的外层函式的变数时,该变数提供的值并非变数建立时的值,而是在父函式范围内的最终值。就是说,当执行绪中执行方法时,方法中的i引数的值,并不是从0累加到2,而是始终是累加道德极限值,也就是3。原来如此,那我们应该如何避免这种陷阱呢?

C#中普遍的做法是,将匿名函式引用的变数用一个临时变数储存下来,然后在匿名函式中使用临时变数。

我们再执行来看,输出依次为 ”张三”,”李四”,”王五”。.注意,每次的输出顺序可能不同,这是由于此处的执行绪执行顺序是由CPU排程的。

闭包并不是针对某一特定语言的概念,而是一个通用的概念。除了在各个支援函数语言程式设计的语言中,我们会接触到它。一些不支援函数语言程式设计的语言中也能支援闭包(如java8之前的匿名内部类)。

2019-06-26 14:22:00

相关文章