什么是闭包
闭包可以从而三个维度来说明。在编程语言领域,闭包是指由函数以及与函数相关的上下文环境组合而成的实体。通过闭包,函数与其上下文变量之间建立起关联关系,上下文变量的状态可以在函数的多次调用过程中持久保持。从作用域而言,私有变量的生命周期被延长,函数调用所生成的值在下次调用时仍被保持。从安全性而言,闭包有利于信息的隐蔽,私有变量只在该函数内可见。
.NET中的闭包
说起闭包,可对会想起JavaScript。在JavaScript语言中,闭包可以说是无处不在。同样,.NET中也有闭包。只不过实现方式和JavaScript不太一样。
通常而言,形成闭包有一些值得总结的非必要条件:
- 嵌套定义函数
- 匿名函数
- 将函数作为参数或返回值
在.NET中,函数并不是第一级成员,所以并不能像JavaScript那样通过嵌套子函数的方式实现闭包。但.NET可以通过匿名委托形成闭包。
delegate void HelloDelegate(); static void Main(string[] args) { string str = "Hello World!"; HelloDelegate hello = delegate() { Print(str); }; } private static void Print(string str) { Console.WriteLine(str); }
反编译上面的IL代码:
如上图所示,编译器自动生成了一个内部类,变量strb变成这个类的字段,即使创建该变量的方法(Main)执行结束,该变量也不会释放,而是在所有回调函数执行之后才被GC回收。这就是.NET实现闭包的原理。
闭包带来的问题
如下面代码:
static void Main() { IList<Action> actions = new List<Action>(); for (int i = 0; i < 5; i++) { actions.Add(() => Console.WriteLine(i)); } foreach (var action in actions) { action(); } }
先猜猜输入的值是什么,如果猜的0、1、2、3、4的话就错了。应该全是4。那为什么呢?因为闭包具有延迟的和数据共享的特性,只有当调用action()方法时才会获取i的值,这是i的值经过i++已经变成4,又因为所有的action都会获取同一个i值,所以最后输出的值都为4。
那怎么解决呢?通常解决方法时在循环中加入中间量。
for (int i = 0; i < 5; i++) { int j = i; actions.Add(() => Console.WriteLine(j)); }