下面的这段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
闭包是什么?
那么闭包究竟是什么呢?目前有两种流行的说法:一种说法是闭包是在其词法上下文中引用了自由变量的函数;另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。两种说法都对,但我更倾向于第二种表述,因为它明确地将闭包定义为“实体”。从例子中我们可以看出,闭包可以表现出对象的特征,而普通的lambda函数或delegate更像是某个方法。结合两种定义,我认为可以把闭包理解为带状态的函数。
自由变量
我们先来看一个闭包的简单例子:
static Func
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 x = 0;
return () => ++x;
}
Func
Console.WriteLine(counter1());
Console.WriteLine(counter1());
Console.WriteLine(counter1());
Func
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
LinkedList
Func
switch (method) {
case Method.Push:
Action
return push;
case Method.Pop:
Func
int value = aList.Last.Value;
aList.RemoveLast();
return value;
};
return pop;
case Method.Top:
Func
return top;
default:
return null;
}
};
return func;
}
NewStack()返回的是一个Func
Action
这就是实际的Push方法!不过,在调用之前还需要显式转换一下类型:
(NewStack()(Method.Push) as Action
最后,我们利用C#3.0的扩展方法(Extension Method)包装一下,让这个调用更加简洁:
public static void Push(this Func
(aStack(Method.Push) as Action
}
public static int Pop(this Func
return (int)(aStack(Method.Pop) as Func
}
public static int Top(this Func
return (int)(aStack(Method.Top) as Func
}
这样,我们就能写出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
5. closureObject接受Method参数,返回表示该方法的闭包(或普通委托)的methodObject;
6. 通过扩展方法为Func
参考
闭包的概念、形式与应用
The Beauty of Closures