|
緣起
每次有新技術(shù)發(fā)布時(shí),我們總能感受到兩種截然不同的情緒:一種是恐懼和抵抗,伴隨著這種情緒的還有諸如"C# 2.0用的挺好的,為什么要在C# 3.0搞到那么復(fù)雜?"或者"我還在使用C#1.0呢?"等言辭;另一種則是興奮和擁抱,伴隨著這種情緒的還有諸如"原來(lái)這個(gè)問(wèn)題在C# 3.0里可以這么簡(jiǎn)單!"等言辭。
最近我在公司內(nèi)部做一個(gè)LINQ的系列講座,在我為其中C#3.0新特性這一講準(zhǔn)備演示文稿時(shí),突然萌生了寫下這篇文章的念頭。語(yǔ)言的特性乃至其本身并沒(méi)有對(duì)錯(cuò)之分,是否接受在很大程度上是一個(gè)感性問(wèn)題,即你是否喜歡這樣的做事方式,我并沒(méi)有打算說(shuō)服任何人接受C# 3.0和LINQ,寫這篇文章也只是想和大家分享一下我自己的感受。
有一次我觀看一個(gè)關(guān)于Expression Blend的培訓(xùn)視頻,里面說(shuō)了一句讓我印象非常深刻的話:
I know how it works because I know why it works.
細(xì)細(xì)品味這句話,你會(huì)感受到它所要傳達(dá)的信息:理解為何需要這個(gè)功能可以幫助你更好地理解如何使用這個(gè)功能,而這也正是我要在這篇文章里采用的表達(dá)方式。
你是如何創(chuàng)建屬性的?
如果你長(zhǎng)期使用C#,相信你不會(huì)對(duì)屬性這個(gè)東西感到陌生。一般地,屬性是對(duì)私有字段的一個(gè)簡(jiǎn)單包裝,就像這樣:
代碼 1
使用屬性而不是直接公開私有字段的一個(gè)好處就是在屬性的獲取訪問(wèn)器或設(shè)置訪問(wèn)器里加入額外的邏輯并不會(huì)為客戶端代碼帶來(lái)麻煩,例如你想在設(shè)置標(biāo)題的時(shí)候做一些額外的檢查。但如果你只是簡(jiǎn)單地包裝一下,像上面的代碼那樣,就會(huì)發(fā)現(xiàn)你其實(shí)多寫了不少可以省略的代碼。既然Title屬性和m_Title私有字段對(duì)應(yīng),獲取訪問(wèn)器就肯定是返回m_Title的值,而設(shè)置訪問(wèn)器也肯定是把值設(shè)到m_Title。再者,如果你只通過(guò)Title屬性來(lái)訪問(wèn)這個(gè)數(shù)據(jù),那么m_Title私有字段就會(huì)變得無(wú)足輕重,這樣的話,為什么不交給編譯器代勞呢?這個(gè)時(shí)候,C# 3.0的自動(dòng)屬性就可以派上用場(chǎng)了:
代碼 2
編譯器會(huì)為你創(chuàng)建一個(gè)私有字段,并讓獲取訪問(wèn)器和設(shè)置訪問(wèn)器指向這個(gè)私有字段。當(dāng)然,如果有需要,例如要在獲取訪問(wèn)器或設(shè)置訪問(wèn)器里加入額外的邏輯時(shí),你隨時(shí)可以對(duì)獲取訪問(wèn)器和設(shè)置訪問(wèn)器進(jìn)行展開。
你是如何初始化對(duì)象的?
現(xiàn)在,假設(shè)我們有這樣一個(gè)類:
代碼 3
你會(huì)怎樣初始化它?一種做法是用Book的默認(rèn)構(gòu)造函數(shù)創(chuàng)建對(duì)象實(shí)例,然后分別為每個(gè)屬性賦值:
代碼 4
另一種做法是使用C# 3.0對(duì)象初始化器:
代碼 5
乍看一下,C#3.0的做法似乎沒(méi)有讓人感到任何優(yōu)越感,現(xiàn)在,請(qǐng)你仔細(xì)觀察一下,這兩份代碼分別包含多少個(gè)";"?代碼4有5個(gè)";",意味著它用了5個(gè)語(yǔ)句進(jìn)行初始化;而代碼5只有1個(gè)";",意味著它只用了1個(gè)語(yǔ)句進(jìn)行初始化。從詞法的角度來(lái)看,如果此刻我只能接受一個(gè)表達(dá)式,那么代碼4的做法就幫不上忙了。一個(gè)變通的方法是為Book類提供帶參的構(gòu)造函數(shù),但這種方法也有弊端,用戶可能只想在初始化時(shí)為部分屬性提供數(shù)據(jù),而我們又無(wú)法確切預(yù)知用戶會(huì)提供哪些屬性的組合,于是,我們可能要為用戶提供足夠多的構(gòu)造函數(shù)重載,嗯,有點(diǎn)無(wú)聊,也有點(diǎn)多余。另一個(gè)變通的方法是提供接受最多參數(shù)的構(gòu)造函數(shù),如果用戶為某個(gè)參數(shù)傳遞null,那么就忽略與之對(duì)應(yīng)的屬性,這個(gè)方法比較接近代碼5的做法,不同的是,如果你的屬性很多,而用戶關(guān)心的只是很少一部分,就可能不得不輸入很多null了。
現(xiàn)在,假設(shè)你要實(shí)例化一組Book對(duì)象,并把它們儲(chǔ)存在一個(gè)集合里,你會(huì)怎么做?下面是通常的做法:
代碼 6
如果結(jié)合使用C# 3.0的對(duì)象初始化器和集合初始化器,你就可以把代碼簡(jiǎn)化為:
代碼 7
集合里的每個(gè)元素通過(guò)","分割,結(jié)合對(duì)象初始化器使用,整個(gè)集合的結(jié)構(gòu)顯得比較明晰。字典的初始化也可以同樣簡(jiǎn)單:
代碼 8
說(shuō)到這里,我相信你也能感覺(jué)到,C#似乎正在表達(dá)式化,以前需要很多條語(yǔ)句才能做到的事情,現(xiàn)在卻可以用單個(gè)表達(dá)式描述出來(lái),而這種理念也滲透在整個(gè)C# 3.0的氛圍里。
你是如何把運(yùn)算邏輯外包出去的?
假設(shè)我現(xiàn)在得到了一組Book的實(shí)例對(duì)象,你要對(duì)它們進(jìn)行排序,那么你如何告訴它你要按價(jià)格來(lái)排序呢?
代碼 9
在C# 1.0里,我們需要特意為它提供一個(gè)獨(dú)立的方法:
代碼 10
然后向Sort()方法傳入所需委托的實(shí)例:
代碼 11
這在C# 2.0里可以進(jìn)一步簡(jiǎn)化為:
代碼 12
如果使用C# 2.0的匿名方法,我們可以省去很多不必要的代碼:
代碼 13
此外,使用匿名方法,Sort()方法和你希望它用來(lái)比較兩個(gè)Book實(shí)例對(duì)象的邏輯可以放在同一個(gè)地方;而使用獨(dú)立的命名方法,包含這個(gè)邏輯的方法可能會(huì)由于整理代碼而被挪到別的地方。這樣,當(dāng)你看到代碼12時(shí),為了了解它內(nèi)部的實(shí)現(xiàn),就不得不花一些精力去尋找Compare()方法了。當(dāng)然,你可以爭(zhēng)辯說(shuō),我們可以制定一個(gè)編碼規(guī)范,使得Compare()方法必須緊貼在Sort()方法的下方。是的,你可以,但如果這個(gè)邏輯并不需要重用,那么使用匿名方法還是具有明顯的優(yōu)勢(shì)的。如果這個(gè)邏輯需要重用,那么匿名方法就無(wú)能為力了。
現(xiàn)在,讓我們來(lái)考察一下代碼13,有沒(méi)有發(fā)現(xiàn)匿名方法的表達(dá)方式還不夠簡(jiǎn)練?我們知道,books集合里面只有Book的實(shí)例對(duì)象,所以Sort()方法傳給我們兩個(gè)參數(shù)的類型必定是Book,而Sort()方法期待的結(jié)果正是x.Price.CompareTo(y.Price)這個(gè)表達(dá)式的運(yùn)算結(jié)果,至于delegate和return這樣的字眼可以說(shuō)在這里完全是多余的,那么為什么我們不直接這樣表達(dá)呢:
代碼 14
這就是C#3.0引入的Lambda表達(dá)式語(yǔ)法。我見(jiàn)過(guò)一些人,他們通常強(qiáng)調(diào)盡可能簡(jiǎn)單,但若事情突然變得比他們預(yù)期的還要簡(jiǎn)單很多,他們就開始感到不適,甚至拒絕接受這種簡(jiǎn)單,其實(shí)即使事物的發(fā)展方向和你的前進(jìn)方向相一致,但如果發(fā)展速度大大超越了你,仍然有可能引發(fā)你內(nèi)心對(duì)失控的恐懼。我希望Lambda表達(dá)式語(yǔ)法不會(huì)讓你感到太大的不適,當(dāng)然我更希望你會(huì)喜歡上它。
Lambda表達(dá)式的理解其實(shí)可以很簡(jiǎn)單,就是"=>"左邊的參數(shù)參與右邊的表達(dá)式運(yùn)算,而運(yùn)算結(jié)果將會(huì)返回,這有點(diǎn)像化合反應(yīng),即兩種或兩種以上的物質(zhì)(左邊的參數(shù))生成一種新物質(zhì)(右邊的表達(dá)式的運(yùn)算結(jié)果),不同的是,Lambda可以不接收任何參數(shù),也可以不返回任何結(jié)果。
"=>"右邊除了可以放表達(dá)式之外,還可以放語(yǔ)句,像這樣:
代碼 15
我們把它稱為L(zhǎng)ambda語(yǔ)句(Lambda Statement),或許你已經(jīng)發(fā)現(xiàn),它和匿名方法相比只是不需要寫delegate關(guān)鍵字和參數(shù)類型。
你是如何為對(duì)象擴(kuò)展與之相關(guān)的功能的?
我一直在想,為什么String類沒(méi)有提供一個(gè)Reverse()方法,把字符串翻轉(zhuǎn)呢?我猜可能是因?yàn)檫@種操作沒(méi)有什么現(xiàn)實(shí)意思,除非你要做一個(gè)文字游戲。實(shí)現(xiàn)Reverse()方法并不難,下面是其中一種做法:
代碼 16
使用方法也非常簡(jiǎn)單:
代碼 17
你甚至可以把Reverse()方法放到某個(gè)靜態(tài)類里,例如Utils,這樣,代碼17就可以變成:
代碼 18
在C# 3.0之前,你最多只能走到這里,而到了C# 3.0,你還可以使用擴(kuò)展方法對(duì)它做進(jìn)一步調(diào)整,使代碼18變成:
代碼 19
怎么樣,看上去就像Reverse()方法是屬于String的,而你所需要做的僅僅是在Reverse()方法的target參數(shù)前面加上"this"關(guān)鍵字:
代碼 20
我們知道,計(jì)算機(jī)的底層世界并不知道什么是面向?qū)ο螅覀冊(cè)趯?duì)象里定義的實(shí)例方法都包含一個(gè)隱藏參數(shù),這個(gè)參數(shù)就是指向當(dāng)前對(duì)象實(shí)例的指針,C#3.0的擴(kuò)展方法在形式上模仿了這種做法,但由于擴(kuò)展方法本質(zhì)上并不屬于與之相關(guān)的類,所以你無(wú)法在擴(kuò)展方法里訪問(wèn)類內(nèi)部的私有成員。
就上面的討論來(lái)說(shuō),你可能認(rèn)為,和代碼18相比,代碼19并沒(méi)有太大的優(yōu)勢(shì),那么為什么需要擴(kuò)展方法呢?假設(shè)我們手頭上有一堆書,我想找到最便宜的LINQ的書,使用標(biāo)準(zhǔn)查詢運(yùn)算符的話可以這樣寫:
代碼 21
我們知道,Where()、OrderBy()和First()等都是擴(kuò)展方法,如果C# 3.0不支持?jǐn)U展方法,那么代碼21就不得不寫成這樣了:
代碼 22
代碼21的可讀性明顯比代碼22的高,也顯得更自然,而此時(shí)我們只是使用了3個(gè)標(biāo)準(zhǔn)查詢運(yùn)算符,你可以想象一下,在沒(méi)有擴(kuò)展方法的支持下要表達(dá)更復(fù)雜的查詢會(huì)是怎樣一番情景?
你是如何表達(dá)你想要的東西的?
現(xiàn)在,假設(shè)我想找到最便宜的LINQ的書,使用C# 2.0的語(yǔ)法,我可能需要這樣:
代碼 23
雖然我已經(jīng)使用了Array.IndexOf()方法、List<T>.Sort()方法和匿名函數(shù)來(lái)簡(jiǎn)化代碼,但仍然無(wú)法掩蓋一個(gè)事實(shí),那就是我在講述如何獲取我想要的東西,而這也正是命令式編程(Imperative Programming)的核心思想。
如果使用C# 3.0的語(yǔ)法,情況將會(huì)大不一樣:
代碼 24
在這里,你表達(dá)了你想要的東西,而不是獲取這些東西的具體步驟,這是聲明式編程(DeclarativeProgramming)的核心思想,這樣做的好處是明顯的,你的需求可以被重新解析并執(zhí)行,必要時(shí)還可以對(duì)底層的實(shí)現(xiàn)進(jìn)行優(yōu)化,但由于你并不關(guān)心和牽扯到具體的實(shí)現(xiàn)上,所以那些優(yōu)化并不會(huì)導(dǎo)致你修改代碼。
命令式編程就像過(guò)程管理,你深入執(zhí)行的細(xì)節(jié),繼而對(duì)整個(gè)過(guò)程的執(zhí)行實(shí)施控制;而聲明式編程則像目標(biāo)管理(MBO),你制定目標(biāo),并把任務(wù)分配下去執(zhí)行。代碼23給人的感覺(jué)就是整個(gè)執(zhí)行過(guò)程都非常的清楚,你可以對(duì)任何一個(gè)步驟進(jìn)行修改或者調(diào)優(yōu);而代碼24給人的感覺(jué)就是你除了說(shuō)出你想要什么,你什么也不能做,這對(duì)于那些過(guò)程管理?yè)泶髡邅?lái)說(shuō)可能是不可接受的,他們感到對(duì)事物失去了控制,無(wú)法建立安全感,因而產(chǎn)生了焦慮。曾經(jīng)有人向我抱怨:如果你使用了LINQ,你就只能迫使自己相信它的實(shí)現(xiàn)是很好的。想想看,如果你的公司把飯?zhí)脴I(yè)務(wù)承包給一個(gè)餐飲公司,你的公司可以插手別人如何招聘廚師、如何采購(gòu)食物、如何燒菜燒飯嗎?選擇LINQ意味著你愿意把執(zhí)行細(xì)節(jié)交給別人去處理,從而脫離這些細(xì)節(jié),如果你根本無(wú)法放下對(duì)這些細(xì)節(jié)的控制,那么LINQ可能并不適合你。
很難說(shuō)這兩種編程方式孰優(yōu)孰劣,因?yàn)樵谀承﹫?chǎng)合下,善于過(guò)程管理的管理者確實(shí)更能讓事態(tài)朝正確的方向發(fā)展;而在另一些場(chǎng)合下,目標(biāo)管理為實(shí)現(xiàn)者提供足夠的自由度,更能激勵(lì)他們積極地進(jìn)行思考。管理界對(duì)于過(guò)程管理和目標(biāo)管理孰優(yōu)孰劣之爭(zhēng)論似乎從來(lái)沒(méi)有停過(guò),更何況編程界對(duì)于命令式編程和聲明式編程孰優(yōu)孰劣之爭(zhēng)論,我個(gè)人倒是更傾向于把這看成是找出更適合你自己的風(fēng)格,而不是盲目聽(tīng)信別人的說(shuō)法。語(yǔ)言到底是發(fā)揮積極作用還是消極作用在很大程度上是取決于使用者的,我們應(yīng)該使用語(yǔ)言有利的一面來(lái)協(xié)助我們的工作,而不是使用其有害的一面來(lái)傷害自己和別人。
回到代碼24,它把滿足條件的書的所有信息都返回給我,如果我只需要書名和作者名字呢?我們知道,在面向?qū)ο蟮氖澜缋铮畔?chǔ)存在對(duì)象里,于是我們不得不走到一個(gè)尷尬的境地,那就是我們要為此創(chuàng)建一個(gè)臨時(shí)類:
代碼 25
噩夢(mèng)正式開始了,如果我需要書名和價(jià)格呢?如果我需要書名、作者和價(jià)格呢?……(讀者可以自行補(bǔ)全這個(gè)列表)這個(gè)時(shí)候就輪到C# 3.0的匿名類型和隱式類型化變量出場(chǎng)了:
代碼 26
因?yàn)槟涿愋褪怯删幾g器自動(dòng)生成的,而在你寫代碼的時(shí)候它還沒(méi)有名字,所以你無(wú)法用這個(gè)類型來(lái)聲明這個(gè)變量,此時(shí)"var"關(guān)鍵字就派上用場(chǎng)了。這個(gè)是"var"關(guān)鍵字的最初目的,但得益于類型推斷系統(tǒng),我們還可以使用"var"關(guān)鍵字聲明任何本地變量,只要我們?cè)诼暶鞯耐瑫r(shí)給予它初始化,否則編譯器無(wú)法進(jìn)行推斷。曾經(jīng)有人問(wèn)我:如果我想返回代碼26里的wanted7怎么辦?我們知道,方法的返回值需要明確給出類型,而在我們寫下代碼26時(shí),編譯器還沒(méi)有給查詢表達(dá)式里的匿名類型取名。如果你真的要把它返回,你只能把方法的返回值類型定為IEnumerable<object>,因?yàn)槲覀冎荒艽_定匿名類型是object的后代,但這樣一來(lái),客戶端代碼的日子就不太好過(guò)了,因?yàn)槌送ㄟ^(guò)反射來(lái)訪問(wèn)你的對(duì)象,它別無(wú)他選。如果你真的要把它返回,那就意味著你和客戶端代碼有共享這個(gè)對(duì)象的需求,此時(shí)恰當(dāng)?shù)淖龇☉?yīng)該是使用命名類型。另外,代碼26里構(gòu)建匿名類型時(shí)的"book.Title"是"Title =book.Title"的簡(jiǎn)寫,當(dāng)你省略"Title ="時(shí),編譯器會(huì)假定你希望匿名類型的這個(gè)屬性的名字和Book.Title的一樣。
匿名類型還有一個(gè)有趣的地方,它曾經(jīng)是可變的(mutable),后來(lái)卻變成不可變的(immutable),Sree在《Immutable is, the new Anonymous Type》一文中給出了這個(gè)轉(zhuǎn)變的解釋。我們知道,在面向?qū)ο蟮氖澜缋铮瑢?duì)象封裝并維護(hù)自身的狀態(tài),我們通過(guò)調(diào)用對(duì)象的方法所產(chǎn)生的副作用來(lái)影響對(duì)象的狀態(tài),而不可變則是函數(shù)式編程(Functional Programming)的核心特征,或許你已經(jīng)感受到了,C#3.0引入了大量函數(shù)式編程的東西,而函數(shù)式編程語(yǔ)言似乎也要風(fēng)生水起,這究竟意味著什么呢?
前路在何方?
無(wú)論你是否承認(rèn),C# 3.0在表達(dá)上比它之前的版本要來(lái)的簡(jiǎn)單,但要獲得這種簡(jiǎn)單,你必須先用很多東西武裝自己的腦袋,這使我想起曾經(jīng)在一本書里看到的一句話:
簡(jiǎn)單是由復(fù)雜來(lái)支撐的。
不同語(yǔ)言之間的相互滲透已經(jīng)不再是什么新奇之事了,引入其它語(yǔ)言的功能有時(shí)候甚至可以看作是在戰(zhàn)略上入侵對(duì)手的市場(chǎng),這在某種程度上有點(diǎn)像金融業(yè)的混業(yè)經(jīng)營(yíng)。下一個(gè)版本的C#將會(huì)是怎樣的呢?或許這個(gè)問(wèn)題令你興奮不已,你甚至希望現(xiàn)在就讓C#Team看看你的創(chuàng)造力;或許這個(gè)問(wèn)題令你痛心不已,你害怕自己無(wú)法適應(yīng)下一波的變革,因?yàn)樽兏锟赡軐?dǎo)致動(dòng)蕩,動(dòng)蕩可能帶來(lái)失控,失控可能引發(fā)焦慮。不管怎樣,該來(lái)的是無(wú)法回避的,或許現(xiàn)在先讓我們看看Matthew Podwysocki的《What Is the Future of C# Anyways?》是否有一些啟示……
NET技術(shù):我眼中的C# 3.0,轉(zhuǎn)載需保留來(lái)源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。