C# 闭包

1. 首先要说明的是, delegate 被编译器 编译成一个class, 因此才能传来传去(具体参考 《CLR via C#》第四版), 因此 Action、Func也是如此javascript

2. 在C#中,原来闭包只是编译器玩的花招而已,它仍然没有脱离.NET对象生命周期的规则,它将须要修改做用域的变量直接封装到返回的类中变成类的一个属性,从而保证了变量的生命周期不会随函数调用结束而结束,由于变量n在这里已经成了返回的类的一个属性. 这个观点引用自 C#与闭包, 写的很好, 将原文也贴出来吧php

 

 

首先想说明一点,虽然有这样那样的很差的心态(好比中文技术书),但整体来讲,国内的技术人员仍是喜欢分享和教导别人的,这点个人我的感觉和以前在园子里看到的朋友的感觉偏偏相反.我的认为其实国内不少技术网友都是很热心的,可能由于语言问题同一个技术热点会稍稍落后国外一些,但一些成熟的或者基础的概念均可以找到很细致的中文介绍,特别是关于闭包,由于它的字面解释确实很绕,因此基本全部试图解释这一名词的同窗都是尽可能用本身认为最通俗易懂的方式来进行讲解.闲话扯远了,这里我就用C#语言来给你们解释下闭包吧.
其实要提到闭包,咱们还得先提下变量做用域和变量的生命周期. 在C#里面,变量做用域有三种,一种是属于类的,咱们常称之为field,第二种则属于函数的,咱们一般称之为局部变量,还有一种,其实也是属于函数的,不过它的做用范围更小,它只属于函数局部的代码片断,这种一样称之为局部变量.这三种变量的生命周期基本均可以用一句话来讲明,每一个变量都属于它所寄存的对象,即变量随着其寄存对象生而生和消亡.对应三种做用域咱们能够这样说,类里面的变量是随着类的实例化而生,同时伴随着类对象的资源回收而消亡(固然这里不包括非实例化的static和const对象).而函数(或代码片断)的变量也随着函数(或代码片断)调用开始而生,伴随函数(或代码片断)调用结束而自动由GC释放,它内部变量生命周期知足先进后出的特性。
那么这里有没有例外呢? 答案是有的,不过在提这点以前,我还须要给各位另一个名词.都说c#就是MS版本的java,这话在.net 1.0可能能够这么说,但自2.0以后C#就能够自豪的说它绝非java了,这里面委托有很大的功劳,若是用过java和C#的人而且尝试过写winform程序时所有手写实现代码的人就会有这样一个感觉,一样的click事件,在java中必需要无故的套个匿名类,但在c#中,你是能够直接将函数名+=到事件以后而不须要显示写上匿名委托的对象类型的,由于编译器会帮你作这部分工做,在3.0和之后的版本之中,微软将委托的用法更是发挥的淋漓精致,不管是简洁的Lamda仍是通俗易懂的LINQ,都是源自委托的.
你可能要问,委托和咱们今天要讲的闭包又有什么关系呢? 咱们知道,c#,java和javascript,ruby,python这些语言不一样,在c#和java的世界里面,原子对象就是类(固然还有struct和基本变量),而不是不少动态语言中的函数,咱们能够实例化一个类,实例化一个变量,但不能够直接new 一个函数.也就是表面上看,咱们是没办法像js那样将函数进行实例化和传递的.这也是为何直到Java 7闭包才被姗姗来迟的加入java特性中。但对C#来讲这些只是表象,我刚学c#的时候,看到最多的解释委托的话就是:委托啊,就至关于c++里面的函数指针啦.这句话虽然笼统,但确实有必定道理,经过委托特别是匿名委托这层对象的包装,咱们就能够突破没法将函数当作对象传递的限制了.
好像这里仍是没讲到闭包和委托的关系,好吧,我太啰嗦了,下面从概念开始讲. 闭包其实就是使用的变量已经脱离其做用域,却因为和做用域存在上下文关系,从而能够在当前环境中继续使用其上文环境中所定义的一种函数对象. 好拗口,程序员,仍是用示例来讲明更好理解. 首先来个最简单的javascript中经常见到的关于闭包的例子:html

这段代码翻译成C#代码就是这样:    java

?
public class TCloser
     {
         public Func< int > T1()
         {
             var n = 999;
             return () =>
             {
                 Console.WriteLine(n);
                 return n;
             };
         }
     }
    
     class Program{
         static void Main(){
             var a = new TCloser();
             var b = a.T1();
             Console.WriteLine(b());
         }
     }

    从上面的代码咱们不难看到,变量n其实是属于函数T1的局部变量,它原本生命周期应该是伴随着函数T1的调用结束而被释放掉的,但这里咱们却在返回的委托b中仍然能调用它,这里正是闭包所展现出来的威力,由于T1调用返回的匿名委托的代码片断中咱们用到了n,而在编译器看来,这些都是合法的,由于返回的委托b和函数T1存在上下文关系,也就是说匿名委托b是容许使用它所在的函数或者类里面的局部变量的,因而编译器经过一系列动做(具体动做咱们后面再说)使b中调用的函数T1的局部变量自动闭合,从而使该局部变量知足新的做用范围。     所以若是你看到.net中的闭包,你就能够像js中那样理解它,因为返回的匿名函数对象是在函数T1中生成的,所以至关于它是属于T1的一个属性。若是你把T1的对象级别往上提高一个层次就很好理解了,这里就至关于T1是一个类,而返回的匿名对象则是T1的一个属性,对属性而言,它能够调用它所寄存的对象T1的任何其余属性或者方法,包括T1寄存的对象TCloser内部的其余属性。若是这个匿名函数会被返回给其余对象调用,那么编译器会自动将匿名函数所用到的方法T1中的局部变量的生命周转期自动提高并与匿名函数的生命周期相同,这样就称之为闭合。         也许你会说,这个返回的委托包含的变量n只是编译器经过某种方式隐藏的对这个委托对象的一个一样对象的赋值吧,那么咱们再对比下面两个方法:    python

?
public class TCloser{
public Func< int > T1()
     {
         var n = 999;
         Func< int > result = () =>
         {
             return n;
         };
 
         n = 10;
         return result;
     }
 
     public dynamic T2()
     {
         var n = 999;
         dynamic result = new { A = n };
         n = 10;
         return result;
     }
     static void Main(){
         var a = new TCloser();
         var b = a.T1();
         var c = a.T2();
         Console.WriteLine(b());
         Console.WriteLine(c.A);
     }
}

    最后输出结果是什么呢?答案是10和999,由于闭包的特性,这里匿名函数中所使用的变量就是实际T1中的变量,与之相反的是,匿名对象result里面的A只是初始化时被赋予了变量n的值,它并非n,因此后面n改变以后A并未随之而改变。这正是闭包的魔力所在。         你可能会好奇.net自己并不支持函数对象,那么这样的特性又是从何而来呢?答案是编译器,咱们一看IL代码便会明白了。     首先我给出c#代码:    c++

?
public class TCloser {
         public Func< int > T1(){
             var n = 10;
             return () =>
             {
                 return n;
             };
         }
 
         public Func< int > T4(){
             return () =>
             {
                 var n = 10;
                 return n;
             };
         }
     }

    这两个返回的匿名函数的惟一区别就是返回的委托中变量n的做用域不同而已,T1中变量n是属于T1的,而在T4中,n则是属于匿名函数自己的。但咱们看看IL代码就会发现这里面的大不一样了:    程序员

?
.method public hidebysig instance class [mscorlib]System.Func`1<int32> T1() cil managed{
     .maxstack 3
     .locals init (
         [0] class ConsoleApplication1.TCloser/<>c__DisplayClass1 CS$<>8__locals2,
         [1] class [mscorlib]System.Func`1<int32> CS$1$0000)
     L_0000: newobj instance void ConsoleApplication1.TCloser/<>c__DisplayClass1::.ctor()
     L_0005: stloc.0
     L_0006: nop
     L_0007: ldloc.0
     L_0008: ldc.i4.s 10
     L_000a: stfld int32 ConsoleApplication1.TCloser/<>c__DisplayClass1::n
     L_000f: ldloc.0
     L_0010: ldftn instance int32 ConsoleApplication1.TCloser/<>c__DisplayClass1::<T1>b__0()
     L_0016: newobj instance void [mscorlib]System.Func`1<int32>::.ctor( object , native int )
     L_001b: stloc.1
     L_001c: br.s L_001e
     L_001e: ldloc.1
     L_001f: ret
}
 
.method public hidebysig instance class [mscorlib]System.Func`1<int32> T4() cil managed
{
     .maxstack 3
     .locals init (
         [0] class [mscorlib]System.Func`1<int32> CS$1$0000)
     L_0000: nop
     L_0001: ldsfld class [mscorlib]System.Func`1<int32> ConsoleApplication1.TCloser::CS$<>9__CachedAnonymousMethodDelegate4
     L_0006: brtrue.s L_001b
     L_0008: ldnull
     L_0009: ldftn int32 ConsoleApplication1.TCloser::<T4>b__3()
     L_000f: newobj instance void [mscorlib]System.Func`1<int32>::.ctor( object , native int )
     L_0014: stsfld class [mscorlib]System.Func`1<int32> ConsoleApplication1.TCloser::CS$<>9__CachedAnonymousMethodDelegate4
     L_0019: br.s L_001b
     L_001b: ldsfld class [mscorlib]System.Func`1<int32> ConsoleApplication1.TCloser::CS$<>9__CachedAnonymousMethodDelegate4
     L_0020: stloc.0
     L_0021: br.s L_0023
     L_0023: ldloc.0
     L_0024: ret
}

看IL代码你就会很容易发现其中究竟了,在T1中,函数对返回的匿名委托构造的是一个类,名称为newobj instance void ConsoleApplication1.TCloser/<>c__DisplayClass1::.ctor(),而在T4中,则是仍然是一个普通的Func委托,只不过级别变为类级别了而已。 那咱们接着看看T1中声明的类c__DisplayClass1是何方神圣:c#

?
. class auto ansi sealed nested private beforefieldinit <>c__DisplayClass1
     extends [mscorlib]System.Object{
     .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
     .method public hidebysig specialname rtspecialname instance void .ctor() cil managed{}
     .method public hidebysig instance int32 <T1>b__0() cil managed{}
     .field public int32 n
}

看到这里想必你已经明白了,在C#中,原来闭包只是编译器玩的花招而已,它仍然没有脱离.NET对象生命周期的规则,它将须要修改做用域的变量直接封装到返回的类中变成类的一个属性n,从而保证了变量的生命周期不会随函数T1调用结束而结束,由于变量n在这里已经成了返回的类的一个属性了。
看到这里我想你们应该大致上了解C#闭包的前因后果了吧,C#中,闭包其实和类中其余属性、方法是同样的,它们的原则都是下一层能够畅快的调用上一层定义的各类设定,但上一层则不具有访问下一层设定的能力。即类中方法里的变量能够自由访问类中的全部属性和方法,而闭包又能够访问它的上一层即方法中的各类设定。但类不能够访问方法的局部变量,同理,方法也不能够访问其内部定义的匿名函数所定义的局部变量。
这正是C#中的闭包,它经过超越java语言的委托打下了闭包的第一步基础,随后又经过各类语法糖和编译器来实现现在在.NET世界全面开花的Lamda和LINQ.也使得咱们可以编写出更加简洁优雅的代码。
附:后面是吐槽,与上文无关,你们能够略过,这篇文章其实两年以前在给同事讲C#闭包的时候就有想法整理出来和你们分享了,不过由于生活,工做,或许主要仍是本身太懒的缘由而拖着没动笔,到今天早上看到园友抱怨国内教书育人的氛围才最终决定利用晚上时间把它整理,而后放出来。我我的认为国内技术圈子的氛围尚可,虽然仍然不少浮躁和易怒在圈子里徘徊。但咱们想一想国内IT人的生存空间就容易理解了。天天最理想的状况朝9晚6的干活,晚上加班,周末加班这些都是常事,而对咱们而言,只要想写出一些通过细细思考的东西都至少须要2个小时以上,并且最好中间不要有人来打扰,这也就注定咱们在白天工做时候很难彻底有时间静下来组织语言,刨掉这些时间,留给咱们本身的生活时间又有多少呢?因此我每次看到有园友发表帖子的时间是晚上1点,2点甚至更晚,都绝不意外, 咱们并不是专业写手,也不像国外IT人那样有充足的闲暇时光能够钻研本身的最爱,咱们赚着他们的零头,买着比他们本子价格更贵的笔记本,担着比他们更高房价的压力来生活,这样的生活条件下咱们这些可爱的社区(不只限于cnblogs,javaeye,phpchina等)Geek们仍然如此活跃和热情,你还能抱怨什么呢?你要知道你看到的每篇文章(若是是工做人士的话)都是他们晚上从9点写到12点的生活点滴啊。
因此,之后不要抱怨国内IT氛围吧,相对这个社会其余各行各业的浮躁,我以为咱们的IT圈子已是很乐于分享的一个群体了。并且除了由于“天下武功,源自欧美,滞后于英语国家”的缘故,咱们有些技术确实要晚些才能跟上国外社区的脚步,但对于一些基础知识的解释,已经有不少中文的文章解释得很不错了。像我之前在理解闭包的时候, javaeye上看到的一大堆,像WIKI,像阮一峰的文章,我我的认为对中文用户是足够了。固然,这只是我我的的观点,你们没必要较劲。
最后一点抱怨就是国内大大小小的抄袭网站,我想这也是影响咱们中文用户查询资料的一个重要因素吧。之前曾经尝试过在baidu,google上搜索本身的文章,但结果至关使人失望,那些抄袭的网站历来都不在意内容,由于这些能够经过抄来解决,并且没必要带原文连接,没必要代表做者。好像东西就是他们本身的同样,他们惟一在意的就是SEO。这也致使我在使用google搜索的时候时常看到同一篇文章出如今某一页的全部搜索结果中,固然,网址是千奇百怪,实在让人无奈。有些网站即便标明了出处和做者,但用心略有险恶的不是给的连接,而是文字。并且,在这些抄袭者中,最让我感到悲哀的是大名鼎鼎的败毒文库,我至少看到不下5篇败毒文库里的文章是来自JE或者CSDN的,但在文库里面只有个文档,你看不到任何做者提示或者原文连接,也许有人会说,这也多是做者本身上传的呀,但我我的认为这种可能性过小了,以国内IT人的风格,对败毒即便谈不上厌恶,也不多有主动去巴巴的,试想,一个国内最大的互联网公司都不尊重IT人的劳动(希望我是错的吧),你又能对其余人说什么呢? 一样看看国外,就我看到的DZone, WindowsPhoneGeek等,每一个都是很明确的给出原文的连接,基本上我不多看到有引用别人文章不给原文连接的文章的。而正是这些不尊重做者劳动的网站对国内互联网资料搜索形成大量的垃圾信息。ruby

相关文章
相关标签/搜索