打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
闭包和对象的关系

闭包和对象的关系

下面的这段C#3.0代码看似再普通不过:

Stack stack = StackFactory.New();
stack.Push(1);
stack.Push(2);
stack.Push(3);
Console.WriteLine(stack.Pop());
Console.WriteLine(stack.Pop());
Console.WriteLine(stack.Pop());

运行结果:

>>3

>>2

>>1

但如果我告诉你Stack并不是一个普通的class Stack,而是一个类型别名:using Stack = System.Func<T1, R1>,它其实是一个委托,你会不会觉得很神奇?说得更清楚一些,StackFatory.New()所创建的不是一个普通对象,而是创建了一个闭包(Closure)。

 

闭包是什么?

那么闭包究竟是什么呢?目前有两种流行的说法:一种说法是闭包是在其词法上下文中引用了自由变量的函数;另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。两种说法都对,但我更倾向于第二种表述,因为它明确地将闭包定义为“实体”。从例子中我们可以看出,闭包可以表现出对象的特征,而普通的lambda函数或delegate更像是某个方法。结合两种定义,我认为可以把闭包理解为带状态的函数。

 

自由变量

我们先来看一个闭包的简单例子:

static Func<int, int> AddX(int x) {
    return (y) => x + y;
}

这里的lambda函数(y) => x + y就是一个闭包,它引用了其词法上下文之外的自由变量x。对AddX(8)求值将用8代换x,即(y)=>8 + y;若再继续求值AddX(8)(100)将得到8 + 100 = 108。

 

状态

下面我们将看到如何使闭包具有状态:

static Func<int> NewCounter() {
    int x = 0;
    return () => ++x;
}

Func<int> counter1 = NewCounter();
Console.WriteLine(counter1());
Console.WriteLine(counter1());
Console.WriteLine(counter1());

Func<int> counter2 = NewCounter();
Console.WriteLine(counter2());
Console.WriteLine(counter2());
Console.WriteLine(counter2());

运行结果:

>>1

>>2

>>3

>>1

>>2

>>3

我们通过NewCounter创建了一个闭包,每次对闭包进行求值都会使其环境的局部变量x增1,这样就体现了闭包的状态。同时,我们注意到局部变量x对于不同的闭包是独立的,counter1和counter2并不共享同一个x。

 

闭包 vs class

这里我们可以和OOP程序做一个对比,如果要用类来实现Counter我们会怎么做呢?

class Counter{ //对应NewCounter

    private int x; //对应局部变量int x

    public Counter() { x = 0; } //new Counter()对应NewCounter()

    public int Count() { return ++x;} //对应() => ++x

}

和 上面的闭包程序相比,从结构上看是不是有些类似呢?Counter类与NewCounter函数对应;new Counter()与NewCounter()对应;Counter类的私有成员x和NewCounter的局部变量x对应;Counter类的 Count()方法与闭包对应。

 

行为

除了状态,我们还需要让闭包具备类似stack.Push()和stack.Pop()这样的对象行为。由于闭包只拥有一个()运算符,需要怎样做才能使其具有多个方法呢?答案是高阶函数(Higher-Order Function)。看刚才Stack的例子:

public enum Method {
    Push, Pop, Top
}

public static Func<Method, object> NewStack() {
    LinkedList<int> aList = new LinkedList<int>();
    Func<Method, object> func = (method) => {
        switch (method) {
            case Method.Push:
                Action<int> push = (int aValue) => { aList.AddLast(aValue); };
                return push;
            case Method.Pop:
                Func<int> pop = () => {
                    int value = aList.Last.Value;
                    aList.RemoveLast();
                    return value;
                };
                return pop;
            case Method.Top:
                Func<int> top = () => { return aList.Last.Value; };
                return top;
            default:
                return null;
        }
    };
    return func;
}

NewStack()返回的是一个Func<Method, object>类型的闭包,它的参数是enum Method类型的,返回值是object。NewStack()(Method.Push)将得到:

Action<int> push = (int aValue) => { aList.AddLast(aValue); };

 

这就是实际的Push方法!不过,在调用之前还需要显式转换一下类型:

(NewStack()(Method.Push) as Action<int>)(1);

 

最后,我们利用C#3.0的扩展方法(Extension Method)包装一下,让这个调用更加简洁:

public static void Push(this Func<Method, object> aStack, int aValue){
    (aStack(Method.Push) as Action<int>)(aValue);
}

public static int Pop(this Func<Method, object> aStack){
    return (int)(aStack(Method.Pop) as Func<int>)();
}

public static int Top(this Func<Method, object> aStack){
    return (int)(aStack(Method.Top) as Func<int>)();
}

 

这样,我们就能写出stack.Push(1), stack.Pop()这样很OO的代码来了!通过这样一步步地探索,不知道您是否对函数式编程的闭包以及它和OOP对象的关系有了更深的理解呢?

 

模式

我们可以把上面在C#3.0中用闭包创建对象的方法归纳为一种固定的模式,不妨称其为闭包工厂模式(Closure Factory Pattern)。模式要点包括:

1. 定义一个工厂类XXFactory,提供创建闭包对象的静态工厂方法New;

2. 在New方法的内定义局部对象作为闭包对象的状态m_States;

3. 定义enum Method表示对象所具有的方法;

4. 在New方法内创建并返回一个引用m_States的闭包closureObject,其类型为Func<Method, object>;

5. closureObject接受Method参数,返回表示该方法的闭包(或普通委托)的methodObject;

6. 通过扩展方法为Func<Method, object>定义扩展方法,为closureObject的方法调用加上语法糖衣。

 

参考

闭包的概念、形式与应用

The Beauty of Closures

posted on 2010-11-01 08:56 Todd Wei 阅读(3824) 评论(13) 编辑 收藏

评论

#1楼 2010-11-01 09:01 顾晓北  

闭包?
  回复引用

#2楼 2010-11-01 09:23 没一句正经的业余程序员  

“一种说法是闭包是在其词法上下文中引用了自由变量的函数”,这是从语法角度讲。
”另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体”,这是从语义角度讲,实际编译器实现可能未必如此。
和lisp、javascript等动态语言的实现相比,c#的闭包示例显得好繁琐。

  回复引用

#3楼[楼主] 2010-11-01 09:31 Todd Wei  

@fzwudc
>>lisp、javascript等动态语言的实现相比,c#的闭包示例显得好繁琐
是的,C#的lambda需要进行类型检查而不是符号演算。
  回复引用

#4楼 2010-11-01 12:48 诺贝尔  

对闭包这个名字感到无语的人飘过
  回复引用

#5楼 2010-11-02 03:59 Ivony...  

其实,没看出实用价值。

不过利用闭包来模拟成员变量,倒并不是新鲜玩意儿,JavaScript就是利用闭包来实现私有成员的。

的确很好玩。
  回复引用

#6楼 2010-11-02 08:14 空逸云  

引用一种说法是闭包是在其词法上下文中引用了自由变量的函数;另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。两种说法都对,但我更倾向于第二种表述

相比较于LZ.我更倾向于第一种.原因其实就是你在使用Lambda/LINQ表达式的时候.调用到"外部"的变量.编译器的生成代码其实是生成一个"闭包"的class.这是我所理解的闭包.而且闭包带来的还有就是你的变量的"域"的延长.
http://www.cnblogs.com/kongyiyun/archive/2010/10/15/1851866.html
  回复引用

#7楼 2010-11-02 17:23 虚假真人  

虽然没完全明白这样做比起类有什么好处,不过非常精彩!
  回复引用

#8楼[楼主] 2010-11-02 18:05 Todd Wei  

@虚假真人
闭包是FP中实现OOP的重要手段,所以,个人认为闭包在FP语言中的意义大于在OOP语言中。在C#中闭包可以简化delegate的创建(否则就只能定义class,然后再创建delegate),更易于使用函数式风格编程,这样更易于把各个模块粘合起来。本文主要是揭示闭包和对象之间的联系。
  回复引用

#9楼 2010-11-02 18:23 Albert Yann  

╮(╯▽╰)╭闭包,这概念从函数式过来的吧?
  回复引用

#10楼 2010-11-03 13:11 Ivony...  

引用Todd Wei:
@虚假真人
闭包是FP中实现OOP的重要手段,所以,个人认为闭包在FP语言中的意义大于在OOP语言中。在C#中闭包可以简化delegate的创建(否则就只能定义class,然后再创建delegate),更易于使用函数式风格编程,这样更易于把各个模块粘合起来。本文主要是揭示闭包和对象之间的联系。



在纯粹函数式语言中,由于所有变量都是“不可变”的,闭包即使能捕获到外界变量,但也只是等于多了几个参数,换言之并不能修改外部变量的值,是不能实现OOP的。
  回复引用

#11楼[楼主] 2010-11-03 13:29 Todd Wei  

@Ivony...
是的,纯函数式中的符号都是引用透明的值语义,没法实现引用语义。stack.Push(1)之后和原来的stack在值语义下是不相等的。所以,如果要在纯函数式下实现stack,写出来只能是类似这个样子:
Pop(Push(Push(New(),1), 2))
  回复引用

#12楼 2011-03-06 21:49 egmkang  

我写过lua的闭包,这个C#的闭包确是太繁琐了
  回复引用

#13楼[楼主] 2011-06-11 13:24 Todd Wei  

@egmkang
没看出来Lua的闭包比C#简单呢?
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
Linux调用栈获取分析及实现
如何特意制造栈缓冲区溢出?(x86 & ARM)
.Neter所应该彻底了解的委托
浅谈C/C++内存泄漏及其检测工具
剑指 Offer 30. 包含min函数的栈
C 语言打印backtrace方法
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服