知识大全 浅谈C#闭包的相关原理

Posted 变量

篇首语:一本书像一艘船,带领我们从狭獈的地方,驶向生活的无限广阔的海洋。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 浅谈C#闭包的相关原理相关的知识,希望对你有一定的参考价值。

浅谈C#闭包的相关原理  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

  首先想说明一点 虽然有这样那样的不好的心态(比如中文技术书) 但总体来说 国内的技术人员还是喜欢分享和教导别人的 这点我的个人感受和之前在园子里看到的朋友的感受恰恰相反 我个人其实国内很多技术网友都是很热心的 可能因为语言问题同一个技术热点会稍稍落后国外一些 但一些成熟的或者基础的概念都可以找到很细致的中文介绍 特别是关于闭包 因为它的字面解释确实很绕 所以基本所有试图解释这一名词的同学都是尽量用自己认为最通俗易懂的方式来进行讲解 闲话扯远了 这里我就用C#语言来给大家解释下闭包吧

  其实要提到闭包 我们还得先提下变量作用域和变量的生命周期

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

  那么这里有没有例外呢?

  答案是有的 不过在提这点之前 我还需要给各位另外一个名词 都说c#就是MS版本的java 这话 可能可以这么说 但自 之后C#就可以自豪的说它绝非java了 这里面委托有很大的功劳 如果用过java和C#的人并且尝试过写winform程序时全部手写实现代码的人就会有这样一个感受 同样的click事件 在java中必须要无端的套个匿名类 但在c#中 你是可以直接将函数名+=到事件之后而不需要显示写上匿名委托的对象类型的 因为编译器会帮你做这部分工作 在 和以后的版本之中 微软将委托的用法更是发挥的淋漓精致 无论是简洁的Lamda还是通俗易懂的LINQ 都是源自委托的

  你可能要问 委托和我们今天要讲的闭包又有什么关系呢?

  我们知道 c# java和javascript ruby python这些语言不同 在c#和java的世界里面 原子对象就是类(当然还有struct和基本变量) 而不是其他语言的函数 我们可以实例化一个类 实例化一个变量 但不可以直接new 一个函数 也就是表面上看 我们是没办法像js那样将函数进行实例化和传递的 这也是为什么直到Java 闭包才被姗姗来迟的加入java特性中 但对C#来说这些只是表象 我刚学c#的时候 看到最多的解释委托的话就是:委托啊 就相当于c++里面的函数指针啦 这句话虽然笼统 但却是有一定道理 通过委托特别是匿名委托这层对象的包装 我们就可以突破无法将函数当做对象传递的限制了

  好像这里还是没讲到闭包和委托的关系 好吧 我太啰嗦了 下面从概念开始讲

  闭包其实就是使用的变量已经脱离其作用域 却由于和作用域存在上下文关系 从而可以在当前环境中继续使用其上文环境中所定义的一种函数对象

  好拗口 程序员 还是用示例来说明更好理解

  首先来个最简单的javascript中常常见到的关于闭包的例子:

  function f ()

  var n= ;

  return function()

  alert(n);

  //

  return n;

  

  

  var a =f ();

  alert(a());

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

  public class TCloser

  

  public Func<int> T ()

  

  var n = ;

  return () =>

  

  Console WriteLine(n);

  return n;

  ;

  

  

  class Program

  static void Main()

  var a =new TCloser();

  var b = a T ();

  Console WriteLine(b());

  

  

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

  因此如果你看中的闭包 你就可以像js中那样理解它 由于返回的匿名函数对象是在函数T 中生成的 因此相当于它是属于T 的一个属性 如果你把T 的对象级别往上提升一个层次就很好理解了 这里就相当于T 是一个类 而返回的匿名对象则是T 的一个属性 对属性而言 它可以调用它所寄存的对象T 的任何其他属性或者方法 包括T 寄存的对象TCloser内部的其他属性 如果这个匿名函数会被返回给其他对象调用 那么编译器会自动将匿名函数所用到的方法T 中的局部变量的生命周转期自动提升并与匿名函数的生命周期相同 这样就称之为闭合

  也许你会说 这个返回的委托包含的变量n只是编译器通过某种方式隐藏的对这个委托对象的一个同样对象的赋值吧 那么我们再对比下面两个方法:

  public class TCloser  public Func<int> T ()    var n = ;  Func<int> result = () =>    return n;  ;  n = ;  return result;    public dynamic T ()    var n = ;  dynamic result =new A = n ;  n = ;  return result;    static void Main()  var a = new TCloser();  var b = a T ();  var c = a T ();  Console WriteLine(b());  Console WriteLine(c A);    最后输出结果是什么呢?答案是 和 因为闭包的特性 这里匿名函数中所使用的变量就是实际T 中的变量 与之相反的是 匿名对象result里面的A只是初始化时被赋予了变量n的值 它并不是n 所以后面n改变之后A并未随之而改变 这正是闭包的魔力所在

  你可能会好本身并不支持函数对象 那么这样的特性又是从何而来呢?答案是编译器 我们一看IL代码便会明白了

  首先我给出c#代码:

  public class TCloser   public Func<int> T ()  var n = ;  return () =>    return n;  ;    public Func<int> T ()  return () =>    var n = ;  return n;  ;    这两个返回的匿名函数的唯一区别就是返回的委托中变量n的作用域不一样而已 T 中变量n是属于T 的 而在T 中 n则是属于匿名函数本身的 但我们看看IL代码就会发现这里面的大不同了:

  thod public hidebysig instance class [mscorlib]System Func` <int > T () cil managed

   maxstack

   locals init (

  [ ] class ConsoleApplication TCloser/<>c__DisplayClass CS$<> __locals

  [ ] class [mscorlib]System Func` <int > CS$ $ )

  L_ : newobj instance void ConsoleApplication TCloser/<>c__DisplayClass :: ctor()

  L_ : stloc

  L_ : nop

  L_ : ldloc

  L_ : ldc i s

  L_ a: stfld int ConsoleApplication TCloser/<>c__DisplayClass ::n

  L_ f: ldloc   L_ : ldftn instance int ConsoleApplication TCloser/<>c__DisplayClass ::<T >b__ ()

  L_ : newobj instance void [mscorlib]System Func` <int >:: ctor(object native int)

  L_ b: stloc

  L_ c: br s L_ e

  L_ e: ldloc

  L_ f: ret

  

  thod public hidebysig instance class [mscorlib]System Func` <int > T () cil managed

  

   maxstack

   locals init (

  [ ] class [mscorlib]System Func` <int > CS$ $ )

  L_ : nop

  L_ : ldsfld class [mscorlib]System Func` <int > ConsoleApplication TCloser::CS$<> __CachedAnonymousMethodDelegate

  L_ : brtrue s L_ b

  L_ : ldnull

  L_ : ldftn int ConsoleApplication TCloser::<T >b__ ()

  L_ f: newobj instance void [mscorlib]System Func` <int >:: ctor(object native int)

  L_ : stsfld class [mscorlib]System Func` <int > ConsoleApplication TCloser::CS$<> __CachedAnonymousMethodDelegate

  L_ : br s L_ b

  L_ b: ldsfld class [mscorlib]System Func` <int > ConsoleApplication TCloser::CS$<> __CachedAnonymousMethodDelegate

  L_ : stloc

  L_ : br s L_

  L_ : ldloc

  L_ : ret

  

  看IL代码你就会很容易发现其中究竟了 在T 中 函数对返回的匿名委托构造的是一个类 名称为newobj instance void ConsoleApplication TCloser/<>c__DisplayClass :: ctor() 而在T 中 则是仍然是一个普通的Func委托 只不过级别变为类级别了而已

  那我们接着看看T 中声明的类c__DisplayClass 是何方神圣:

   class auto ansi sealed nested private beforefieldinit <>c__DisplayClass   extends [mscorlib]System Object

   custom instance void [mscorlib]System Runtime CompilerServices CompilerGeneratedAttribute:: ctor()

  thod public hidebysig specialname rtspecialname instance void ctor() cil managed

  thod public hidebysig instance int <T >b__ () cil managed

   field public int n 

  看到这里想必你已经明白了 在C#中 原来闭包只是编译器玩的花招而已 它仍然没有脱离 NET对象生命周期的规则 它将需要修改作用域的变量直接封装到返回的类中变成类的一个属性n 从而保证了变量的生命周期不会随函数T 调用结束而结束 因为变量n在这里已经成了返回的类的一个属性了

  看到这里我想大家应该大体上了解闭包的来龙去脉了吧 闭包其实和类中其他属性 方法是一样的 它们的原则都是下一层可以畅快的调用上一层定义的各种设定 但上一层则不具备访问下一层设定的能力 即类中方法里的变量可以自由访问类中的所有属性和方法 而闭包又可以访问它的上一层即方法中的各种设定 但类不可以访问方法的局部变量 同理 方法也不可以访问其内部定义的匿名函数所定义的局部变量

  这正是C#中的闭包 它通过超越java语言的委托打下了闭包的第一步基础 随后又通过各种语法糖和编译器来实现如今在 NET世界全面开花的Lamda和LINQ 也使得我们能够编写出更加简洁优雅的代码

  附:后面是吐槽 与上文无关 大家可以略过 这篇文章其实两年之前在给同事讲C#闭包的时候就有想法整理出来和大家分享了 不过因为生活 工作 或许主要还是自己太懒的原因而拖着没动笔 到今天早上看到园友抱怨国内教书育人的氛围才最终决定利用晚上时间把它整理 然后放出来 我个人认为国内技术圈子的氛围尚可 虽然仍然很多浮躁和易怒在圈子里徘徊 但我们想想国内IT人的生存空间就容易理解了 每天最理想的情况朝 晚 的干活 晚上加班 周末加班这些都是常事 而对我们而言 只要想写出一些经过细细思考的东西都至少需要 个小时以上 而且最好中间不要有人来打扰 这也就注定我们在白天工作时候很难完全有时间静下来组织语言 刨掉这些时间 留给我们自己的生活时间又有多少呢?所以我每次看到有园友发表帖子的时间是晚上 点 点甚至更晚 都毫不意外

cha138/Article/program/net/201311/12547

相关参考

知识大全 浅谈C#随机数发生器

浅谈C#随机数发生器  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  我们在做能自动生成试卷的考试

知识大全 浅谈web上存漏洞及原理分析、防范方法

 代码如下:if(isset($_FILES[img]))$file=save_file($_FILES[img]);if($file===false)exit(上存失败!); e

知识大全 在Groovy1.0中使用闭包和集合

在Groovy1.0中使用闭包和集合  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  闭包  闭包

知识大全 Java中的闭包与回调

Java中的闭包与回调  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  闭包是一个可调用的对象它记

知识大全 PHP 5.3闭包语法初探

PHP5.3闭包语法初探  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  PHP将加入闭包语法也就

知识大全 基于javascript 闭包基础分享

  如果对作用域函数为独立的对象这样的基本概念理解较好的话理解闭包的概念并在实际的编程实践中应用则颇有水到渠成之感在DOM的事件处理方面大多数程序员甚至自己已经在使用闭包了而不自知在这种情况下对于浏览

知识大全 闭包会简化Java吗

闭包会简化Java吗  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  WillClosuresMa

知识大全 js实现拖拽 闭包函数详细介绍

  js拖拽  采用简单的闭包实现方式  代码如下:  /**  *CreatedwithJetBrainsWebStorm  *User:lsj  *Date:  *Time:下午:  *Tocha

知识大全 javascript闭包的高级使用方法实例

这篇文章介绍了javascript闭包的高级使用方法实例有需要的朋友可以参考一下   扩展Code:复制代码代码如下:varblogModule=(function(my) myA

知识大全 JavaScript自执行闭包的小例子

JavaScript自执行闭包的小例子  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!JQuery是