閉包和對象的關(guān)系下面的這段C#3.0代碼看似再普通不過: Stack stack = StackFactory.New(); 運(yùn)行結(jié)果: >>3 >>2 >>1 但如果我告訴你Stack并不是一個普通的class Stack,而是一個類型別名:using Stack = System.Func<T1, R1>,它其實(shí)是一個委托,你會不會覺得很神奇?說得更清楚一些,StackFatory.New()所創(chuàng)建的不是一個普通對象,而是創(chuàng)建了一個閉包(Closure)。
閉包是什么? 那么閉包究竟是什么呢?目前有兩種流行的說法:一種說法是閉包是在其詞法上下文中引用了自由變量的函數(shù);另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。兩種說法都對,但我更傾向于第二種表述,因?yàn)樗鞔_地將閉包定義為“實(shí)體”。從例子中我們可以看出,閉包可以表現(xiàn)出對象的特征,而普通的lambda函數(shù)或delegate更像是某個方法。結(jié)合兩種定義,我認(rèn)為可以把閉包理解為帶狀態(tài)的函數(shù)。
自由變量 我們先來看一個閉包的簡單例子: static Func<int, int> AddX(int x) { 這里的lambda函數(shù)(y) => x + y就是一個閉包,它引用了其詞法上下文之外的自由變量x。對AddX(8)求值將用8代換x,即(y)=>8 + y;若再繼續(xù)求值A(chǔ)ddX(8)(100)將得到8 + 100 = 108。
狀態(tài) 下面我們將看到如何使閉包具有狀態(tài): static Func<int> NewCounter() { Func<int> counter1 = NewCounter(); Func<int> counter2 = NewCounter(); 運(yùn)行結(jié)果: >>1 >>2 >>3 >>1 >>2 >>3 我們通過NewCounter創(chuàng)建了一個閉包,每次對閉包進(jìn)行求值都會使其環(huán)境的局部變量x增1,這樣就體現(xiàn)了閉包的狀態(tài)。同時,我們注意到局部變量x對于不同的閉包是獨(dú)立的,counter1和counter2并不共享同一個x。
閉包 vs class 這里我們可以和OOP程序做一個對比,如果要用類來實(shí)現(xiàn)Counter我們會怎么做呢? class Counter{ //對應(yīng)NewCounter private int x; //對應(yīng)局部變量int x public Counter() { x = 0; } //new Counter()對應(yīng)NewCounter() public int Count() { return ++x;} //對應(yīng)() => ++x } 和 上面的閉包程序相比,從結(jié)構(gòu)上看是不是有些類似呢?Counter類與NewCounter函數(shù)對應(yīng);new Counter()與NewCounter()對應(yīng);Counter類的私有成員x和NewCounter的局部變量x對應(yīng);Counter類的 Count()方法與閉包對應(yīng)。
行為 除了狀態(tài),我們還需要讓閉包具備類似stack.Push()和stack.Pop()這樣的對象行為。由于閉包只擁有一個()運(yùn)算符,需要怎樣做才能使其具有多個方法呢?答案是高階函數(shù)(Higher-Order Function)??磩偛臩tack的例子: public enum Method { public static Func<Method, object> NewStack() { NewStack()返回的是一個Func<Method, object>類型的閉包,它的參數(shù)是enum Method類型的,返回值是object。NewStack()(Method.Push)將得到: Action<int> push = (int aValue) => { aList.AddLast(aValue); };
這就是實(shí)際的Push方法!不過,在調(diào)用之前還需要顯式轉(zhuǎn)換一下類型: (NewStack()(Method.Push) as Action<int>)(1);
最后,我們利用C#3.0的擴(kuò)展方法(Extension Method)包裝一下,讓這個調(diào)用更加簡潔: public static void Push(this Func<Method, object> aStack, int aValue){ public static int Pop(this Func<Method, object> aStack){ public static int Top(this Func<Method, object> aStack){
這樣,我們就能寫出stack.Push(1), stack.Pop()這樣很OO的代碼來了!通過這樣一步步地探索,不知道您是否對函數(shù)式編程的閉包以及它和OOP對象的關(guān)系有了更深的理解呢?
模式 我們可以把上面在C#3.0中用閉包創(chuàng)建對象的方法歸納為一種固定的模式,不妨稱其為閉包工廠模式(Closure Factory Pattern)。模式要點(diǎn)包括: 1. 定義一個工廠類XXFactory,提供創(chuàng)建閉包對象的靜態(tài)工廠方法New; 2. 在New方法的內(nèi)定義局部對象作為閉包對象的狀態(tài)m_States; 3. 定義enum Method表示對象所具有的方法; 4. 在New方法內(nèi)創(chuàng)建并返回一個引用m_States的閉包c(diǎn)losureObject,其類型為Func<Method, object>; 5. closureObject接受Method參數(shù),返回表示該方法的閉包(或普通委托)的methodObject; 6. 通過擴(kuò)展方法為Func<Method, object>定義擴(kuò)展方法,為closureObject的方法調(diào)用加上語法糖衣。
參考 標(biāo)簽: oop, functional-programming
評論#1樓 2010-11-01 09:01 顧曉北
閉包?
回復(fù)引用
#2樓 2010-11-01 09:23 沒一句正經(jīng)的業(yè)余程序員
“一種說法是閉包是在其詞法上下文中引用了自由變量的函數(shù)”,這是從語法角度講。
回復(fù)引用
”另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體”,這是從語義角度講,實(shí)際編譯器實(shí)現(xiàn)可能未必如此。 和lisp、javascript等動態(tài)語言的實(shí)現(xiàn)相比,c#的閉包示例顯得好繁瑣。 #3樓[樓主] 2010-11-01 09:31 Todd Wei回復(fù)引用 #4樓 2010-11-01 12:48 諾貝爾
對閉包這個名字感到無語的人飄過
回復(fù)引用
#5樓 2010-11-02 03:59 Ivony...
其實(shí),沒看出實(shí)用價值。
回復(fù)引用
不過利用閉包來模擬成員變量,倒并不是新鮮玩意兒,JavaScript就是利用閉包來實(shí)現(xiàn)私有成員的。 的確很好玩。 #6樓 2010-11-02 08:14 空逸云
相比較于LZ.我更傾向于第一種.原因其實(shí)就是你在使用Lambda/LINQ表達(dá)式的時候.調(diào)用到"外部"的變量.編譯器的生成代碼其實(shí)是生成一個"閉包"的class.這是我所理解的閉包.而且閉包帶來的還有就是你的變量的"域"的延長. http://www.cnblogs.com/kongyiyun/archive/2010/10/15/1851866.html #7樓 2010-11-02 17:23 虛假真人
雖然沒完全明白這樣做比起類有什么好處,不過非常精彩!
回復(fù)引用
#8樓[樓主] 2010-11-02 18:05 Todd Wei
@
虛假真人
回復(fù)引用
閉包是FP中實(shí)現(xiàn)OOP的重要手段,所以,個人認(rèn)為閉包在FP語言中的意義大于在OOP語言中。在C#中閉包可以簡化delegate的創(chuàng)建(否則就只能定義class,然后再創(chuàng)建delegate),更易于使用函數(shù)式風(fēng)格編程,這樣更易于把各個模塊粘合起來。本文主要是揭示閉包和對象之間的聯(lián)系。 #9樓 2010-11-02 18:23 Albert Yann
╮(╯▽╰)╭閉包,這概念從函數(shù)式過來的吧?
回復(fù)引用
#10樓 2010-11-03 13:11 Ivony...
在純粹函數(shù)式語言中,由于所有變量都是“不可變”的,閉包即使能捕獲到外界變量,但也只是等于多了幾個參數(shù),換言之并不能修改外部變量的值,是不能實(shí)現(xiàn)OOP的。 #11樓[樓主] 2010-11-03 13:29 Todd Wei
@
Ivony...
回復(fù)引用
是的,純函數(shù)式中的符號都是引用透明的值語義,沒法實(shí)現(xiàn)引用語義。stack.Push(1)之后和原來的stack在值語義下是不相等的。所以,如果要在純函數(shù)式下實(shí)現(xiàn)stack,寫出來只能是類似這個樣子: Pop(Push(Push(New(),1), 2)) #12樓 2011-03-06 21:49 egmkang
我寫過lua的閉包,這個C#的閉包確是太繁瑣了
回復(fù)引用
#13樓[樓主] 2011-06-11 13:24 Todd Wei
@
egmkang 沒看出來Lua的閉包比C#簡單呢? |
|