前言
在一个机缘巧合之下,了解到了闭包概念,当时对里面的实现感觉没有头绪,所以就暂时放过不去研究了。前段时间看到一本《代码的未来》里面系统性的介绍了一下闭包概念,感觉自己找到点头绪,然后就开始仔细研究一波。
闭包所需要的知识点
- 函数对象
- 作用域
- 生存周期
函数对象
所谓的函数对象:就是作为对象来使用的函数,在c#中为委托(delegate),这里的对象不是面向对象中的那个对象,而是编程语言中的数据对象。
delegate void dhello();
void hello (){
Console.WriteLine("Hello");
}
dhello d = hello;
d();
上述代码为简单的定义一个委托,然后给委托对象赋值一个函数,然后执行这个对象。在delegate的定义中签名必须和被委托的方法的签名一致,形参也必须得一致。就可以直接调用该方法,或者把它当成参数传递给另一个方法调用。在.NET Framework 2.0引入了匿名委托
delegate void dhello();
dhello d = delegate{
Console.WriteLine("Hellp");
}
d();
在这里委托正文就是一直表达式,可以不用定义方法名。这样叫做创建的“内联”委托,无需指定任何其他类型或者方法,只需在所需位置内联委托定义就好。
在c# 3.0版本中,我们对上述代码还能在简易点,我们引入lambda表达式来实现上述功能
delegate void dhello();
dhello d = ()=>{
Console.WriteLine("Hello");
}
d();
这个只是使用委托的更方便的语法。它们将声明签名和方法正文,但在分配到委托之前没有自己的正式标识。与委托不同,可将其作为事件注册的左侧内容或在各种LINQ子句和方法中直接分配。
高阶函数
函数对象最大的用途就是高阶函数。所谓的高阶函数,就是用函数作为参数的函数。
delegate void hello();
delegate void hello1(hello h);
void hello2 (hello h){
h()
}
hello1 h1 = hello2;
hl(()=>{Console.WriteLine("Hello");});
通过将一部分处理以函数对象的形式转移到外部,从而实现了算法的通用性。
函数指针的局限
函数指针的方法体是不能访问外部的局部变量的,如果把这个变量变成全局的是可以的,但是只是为了能访问到而变成一个全局变量是不划算的。
作用域
作用域是指变量的有效范围,也就是某个变量可以被访问的范围。作用域是嵌套的,因此位于内侧的代码块可以访问以其自身为作用域的变量,以及以外侧代码块为作用域的变量。
delegate void hello2(int n);
delegate void hello3(List<int> l, hello2 h2);
void foreached(List<int> list1, hello2 h2)
{
int i = 0;
while (i < list1.Count)
{
h2(list1[i]);
i++;
}
}
List<int> a1 = new List<int>() { 1, 2, 3, 4, 5 };
int i = 0;
hello3 h3 = foreached;
h3(a1, (int c) => { Console.WriteLine("index" + i + "=" + c);i++; });
在函数对象中能对外部变量i进行访问(引用,更新),这就是闭包的构成要件之一。
按照作用域的思路,可能大家觉得上述闭包性质也是理所当然的。但是我们如果加入另外一个概念-生存周期,结果可能就会出乎意料了。
生存周期
所谓的生存周期,就是变量的寿命。相对于表示程序中变量可见范围的作用域来说,生存周期这个概念指的是一个变量可以在多长的周期范围内存在并被能够被访问。
delegate void hello();
hello extent()
{
int n = 0;
return () => { n++; Console.WriteLine("n=" + n); };
}
hello h = extent();
h();
h();
输出:n = 1
n = 2
这个函数变量在每次执行的时候,局部变量n都会被更新,从而输出逐次累加的的结果。按道理n是在extent函数中声明的,函数都已经执行完毕了。变量脱离了作用域之后不应该就消失吗?从这个结果来看,这个变量在函数执行完毕以后没有消失,还在某一个地方继续存活下来的。
这就是生命周期。也就是说,这个从属于外部作用域的局部变量,被函数对象给“封闭”在里面了。
闭包这个词原本就是封闭的意思。被封闭起来的变量的寿命,与封闭它的函数对象寿命相等。也就是说,当封闭这个变量的函数对象不再被访问,被垃圾回收器回收的以后,这个变量的寿命也就同时终结了。
在函数对象中,将局部变量这一环境封闭起来的结构被称为闭包。