技术开发 频道

C#与.NET闭包对比示例解析

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

function f1(){
  
var n=999;
  
return function(){
    alert(n);
// 999
return n;
  }
}
var a =f1();
alert(a());

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

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());
        }
    }
2
相关文章