全面總結(jié).NET 4.0新特性:C#和VB.NET的取長(zhǎng)補(bǔ)短
譯文【51CTO精選譯文】.NET Framework的每一個(gè)新版本都給我們來(lái)帶許多讓.NET變得更強(qiáng)大和易用的新特性,.NET 4.0版當(dāng)然也不例外。當(dāng)我們關(guān)注一個(gè)個(gè)單獨(dú)的新特性時(shí),就會(huì)看到微軟為兌現(xiàn)“聯(lián)合發(fā)展”的諾言,正在C#和VB.NET之間相互取長(zhǎng)補(bǔ)短。
動(dòng)態(tài)查詢(xún)(Dynamic Lookup)(C#中新引入)
前面我們提到過(guò),C#新增了一個(gè)叫做動(dòng)態(tài)(dynamic)的全新靜態(tài)類(lèi)型。雖然我們很多情況下它都能運(yùn)行,但是并不太常用。你可以把動(dòng)態(tài)類(lèi)型當(dāng)成一種支持延遲綁定的對(duì)象。
- dynamic Car = GetCar(); //Get a reference to the dynamic object
 - Car.Model = "Mondeo"; //Assign a value to a property (also works for fields)
 - Car.StartEngine(); //Calling a method
 - Car.Accelerate(100); //Call a methods with parameters
 - dynamic Person = Car["Driver"]; //Getting with an indexer
 - Car["Passenger"] = CreatePassenger(); //Setting with an indexer
 
在編譯時(shí),對(duì)動(dòng)態(tài)對(duì)象的字段、屬性以及方法都基本上被忽略了。也就是說(shuō),即使某個(gè)成員不可用,在編譯時(shí)也不會(huì)提示編譯錯(cuò)誤。因?yàn)槟切┬畔r(shí)只有在運(yùn)行時(shí)才會(huì)可用,.NET 知道如何使用DLR來(lái)解析動(dòng)態(tài)成員。C#至今為止仍然是一門(mén)靜態(tài)類(lèi)型語(yǔ)言,就是因?yàn)樾阅苌系目紤]。新提供的這種動(dòng)態(tài)類(lèi)型并不就意味著你可以完全拋棄靜態(tài)類(lèi)型,它只是你不得不使用動(dòng)態(tài)類(lèi)型時(shí)可以用的一個(gè)工具。另外別忘了,VB .NET中已經(jīng)支持動(dòng)態(tài)查詢(xún)(Dynamic Lookup)了。更多有關(guān)C# 4.0中的動(dòng)態(tài)類(lèi)型的使用方法,可參考51CTO之前發(fā)布的在Visual Studio 2010中使用C# 4.0的動(dòng)態(tài)類(lèi)型一文。
命名和可選參數(shù)(Named and Optional Parameters)(C#中新引入)
命名和可選參數(shù)已出現(xiàn)在VB.NET相當(dāng)長(zhǎng)的一段時(shí)間,現(xiàn)在C#終于也支持了。顧名思義,可選參數(shù)是你可以選擇性地向方法或者構(gòu)造函數(shù)傳遞參數(shù)。如果你的選擇是不傳遞參數(shù),那么被調(diào)用的這個(gè)方法就會(huì)使用之前定義的默認(rèn)值。在C#中,要把一個(gè)方法的參數(shù)變成可選參數(shù),只需要賦給它一個(gè)默認(rèn)值就可以了。
- public void CreateBook(string title="No Title", int pageCount=0, string isbn = "0-00000-000-0")
 - {
 - this.Title = title;
 - this.PageCount = pageCount;
 - this.ISBN = isbn;
 - }
 
你可以用下面這幾種形式調(diào)用上面定義的CreateBook方法
- CreateBook();
 - CreateBook("Some Book Title");
 - CreateBook("Some Book Title", 600);
 - CreateBook("Some Book Title", 600, "5-55555-555-5");
 
這里要注意的是可靠參數(shù)的位置是很重要的。在這個(gè)例子中,title必須是以第一個(gè)參數(shù)出現(xiàn),page count作為第二個(gè),然后ISBN作為第三個(gè)。如果你想調(diào)用CreateBook ,但只傳遞ISBN號(hào)碼這個(gè)參數(shù),那么你有兩種方案可以實(shí)現(xiàn)這點(diǎn)。第一種方案是創(chuàng)建一個(gè)以ISBN為參數(shù)的重載方法,這種方法是很經(jīng)典的辦法,但是也是較為繁冗的。第二種方案是使用命名參數(shù)(named parameter),這種方案比前一種要簡(jiǎn)潔得多。命名參數(shù)可以讓你以任何順序傳遞參數(shù),你只要提供參數(shù)的名字就可以了。這時(shí),你可以用以下幾種形式調(diào)用方法:
- CreateBook(isbn: "5-55555-5555-5");
 - CreateBook("Book Title", isbn: "5-55555-5555-5");
 - CreateBook(isbn: "5-55555-5555-5", title: "Book Title", pageCount: 600);
 
請(qǐng)注意,最開(kāi)始你可以使用位置參數(shù)(positional parameter),然后再像用我們上面演示的第二種方法,但是如果你用上了命名參數(shù)(named parameter),那就必須得一直用下去。
動(dòng)態(tài)導(dǎo)入(Dynamic Import)(C#中新引入 )
幾乎所有通過(guò)COM API暴露出的接口都是使用可變數(shù)據(jù)類(lèi)型,以前在C#中這是用數(shù)據(jù)類(lèi)型對(duì)象表示的。在此之前,C#中是沒(méi)有辦法處理動(dòng)態(tài)類(lèi)型的,所以對(duì)這些類(lèi)型的處理就變成了各種各樣數(shù)據(jù)類(lèi)型中的相互轉(zhuǎn)換。不過(guò)現(xiàn)在C#中開(kāi)始支持動(dòng)態(tài)類(lèi)型,你就可以把COM組件當(dāng)作動(dòng)態(tài)對(duì)象導(dǎo)入了,這樣就可以不用顯式轉(zhuǎn)換對(duì)象類(lèi)型而直接設(shè)置屬性、調(diào)用方法了。
省略引用參數(shù)(Ref Parameter)(C#中新引入)
調(diào)用COM API所帶來(lái)的另一個(gè)副產(chǎn)物就是,大量方法的參數(shù)必須通過(guò)引用傳遞。在大多數(shù)情況下,我們都只是想傳一個(gè)值給方法,而不關(guān)心它返回的是什么。盡管如此,你依然需要?jiǎng)?chuàng)建許多臨時(shí)變量來(lái)保存結(jié)果。這種單調(diào)乏味的工作可以交給實(shí)習(xí)生來(lái)做,讓他們獲得所謂“實(shí)際工作經(jīng)驗(yàn)”。在C#4.0里,你可以向COM里直接傳遞參數(shù)值,編譯器會(huì)自動(dòng)幫你生成臨時(shí)變量。從而節(jié)省開(kāi)發(fā)人員時(shí)間,也讓實(shí)習(xí)生喪失了很多所謂“實(shí)際工作經(jīng)驗(yàn)”。
協(xié)變(Co-variance)和逆變(Contra-variance)(C#和VB .NET中新引入)
泛型(generics)中最驚人的一個(gè)問(wèn)題已經(jīng)在.NET 4.0中得到解決。以前,如果你有一個(gè)支持 IEnumerable < String>的對(duì)象 , 隨后你想把它傳遞給一個(gè)需要 IEnumerable < object> 型參數(shù)的方法,你會(huì)發(fā)現(xiàn)這根本無(wú)法做到。你得生成一個(gè)新的支持 IEnumerable < object> 的對(duì)象,用從IEnumerable實(shí)例中獲得的字符串填充它,然后再把它傳遞給方法。我們都知道,字符串是比對(duì)象更具體的類(lèi)型,因此,我們理所當(dāng)然地認(rèn)為 List< string> 應(yīng)該支持 IEnumerable < string> 接口和 IEnumerable < object> 。結(jié)果是,編譯器并不會(huì)這樣做。不過(guò),在.NET 4.0里,這個(gè)問(wèn)題已經(jīng)得到了解決。因?yàn)楝F(xiàn)在泛型(generics)已經(jīng)支持協(xié)變和異變。
協(xié)變和逆變都是關(guān)乎到程序的類(lèi)型安全和性能的。粗略地說(shuō),協(xié)變表示可以認(rèn)為某個(gè)對(duì)象具有弱派生性(less derived),只要在常規(guī)類(lèi)型的參數(shù)前加上out關(guān)鍵字就表示協(xié)變了。協(xié)變類(lèi)型被限制在輸出位置中使用,也就是說(shuō)它們只有在調(diào)用方法或者訪問(wèn)屬性的結(jié)果里出現(xiàn)。這些就是協(xié)變類(lèi)型能稱(chēng)得上”安全“的唯一地方,或者說(shuō)是唯一一個(gè)在編譯時(shí)不需要進(jìn)行額外的類(lèi)型檢查的地方。在.NET4.0中 , IEnumerable < T> 接口也就等同于 IEnumerable < out T> 因?yàn)镮Enumerable是協(xié)變的。這也意味著下面的例子是完全有效的:
- IEnumerable< string> strings = GetStrings();
 - IEnumerable< object> objects = strings;
 
逆變(Contra-variance)表示可以認(rèn)為某個(gè)對(duì)象具有強(qiáng)派生性(more derived),它可以通過(guò)在普通參數(shù)類(lèi)型前加上in關(guān)鍵字修飾表示。逆變類(lèi)型是限制在輸入位置使用的,也就是說(shuō)它只能出現(xiàn)在方法的參數(shù)中或者說(shuō)必須是擁有”只寫(xiě)“屬性。在.NET中4.0 中, IComparer < T> 接口現(xiàn)在變成了 IComparer < in T> ,因?yàn)镮Comparer是異變的。這個(gè)概念理解起來(lái)不太容易,但是領(lǐng)會(huì)了它們的含義之后能夠免去泛型轉(zhuǎn)換中的許多麻煩。有關(guān)C# 4.0中的協(xié)變和逆變,可參考51CTO之前發(fā)布的C# 4.0中泛型協(xié)變性和逆變性詳解一文。
無(wú)需主互操作程序集(Primary Interop Assemblies)編譯(C#和VB .NET中新引入)
主互操作程序集(Primary Interop Assemblies, PIA)是廠商提供的程序集,它處于COM組件和.NET Framework之間,其中最廣為人知的是微軟Office 主互操作程序集。在開(kāi)發(fā)過(guò)程中,如果你的程序集里有對(duì)PIA的引用,那么就必須在布署程序集時(shí)附帶上PIA,或者提供如何獲得PIA的說(shuō)明。C#和VB .NET的新特性允許你直接把PIA嵌入到自己的程序集中,從而大大簡(jiǎn)化布署。PIA往往比較大,所以把它整個(gè)包含進(jìn)去可能會(huì)使得你的程序集臃腫很多。幸運(yùn)的是,編譯器會(huì)優(yōu)化地選擇只嵌入你實(shí)際上用到的那一部分PIA,這樣在你只用到了PIA的一小部分時(shí)能有效減小PIA的點(diǎn)位面積(footprint)。
#p#
匿名方法的支持(VB.NET 中新引入)
VB.NET新引入的另一個(gè)特性就是是內(nèi)置(inline)或匿名(anonymous)方法。匿名方法這個(gè)名稱(chēng)是非常貼切的,因?yàn)樗试S你直接定義子方法(Subs)和函數(shù),而不用另外在你的類(lèi)里面再添加一個(gè)頂層(top-level)的方法,從而使這個(gè)方法隱藏起來(lái)(也就是匿名)。匿名方法還可以訪問(wèn)它所在代碼塊的所有可用變量,這樣的話(huà),定義匿名方法時(shí)甚至可以不需要用參數(shù)就可以實(shí)現(xiàn)數(shù)值的傳入和返回。在現(xiàn)在通常使用AddressOf 關(guān)鍵字指向一個(gè)方法的地方你都可以定義一個(gè)匿名函數(shù),所以它最大用處可能在于事件處理,如下例所示:
- Dim MyTimer As New System.Timers.Timer(1000)
 - Dim Seconds As Integer = 0
 - AddHandler MyTimer.Elapsed,
 - Sub()
 - Seconds += 1
 - Console.WriteLine(Seconds.ToString() & " seconds have elapsed")
 - End Sub
 - MyTimer.Start()
 - Console.WriteLine("Press any key to exit")
 - Console.ReadLine()
 
注意對(duì)定義器的超時(shí)事件處理程序就是內(nèi)嵌的,而且這個(gè)內(nèi)嵌的方法還直接訪問(wèn)了在它之外定義的變量。您還可以定義內(nèi)嵌函數(shù):
- Dim f = Function(a As Integer, b As Integer)
 - Return a + b
 - End Function
 - Dim x = 10
 - Dim y = 20
 - Dim z = f(x, y)
 
如果一個(gè)內(nèi)嵌函數(shù)在代碼塊的上下文語(yǔ)境里有意義的話(huà),那用起來(lái)確實(shí)是很方便,但是用了它之后很有可能會(huì)影響程序的重用性。
隱式續(xù)行(Implicit line continuation)(VB .NET中新引入)
看C#代碼時(shí),你一眼就可以看出來(lái)語(yǔ)句的末尾在哪里,因?yàn)樗苑痔?hào)作為語(yǔ)句的結(jié)束符。VB也有一個(gè)語(yǔ)句結(jié)束符,但是它的結(jié)束符是是回車(chē),每個(gè)語(yǔ)句都被假設(shè)是同一行里。如果你打想打破這個(gè)規(guī)范,那就不得不使用下劃線來(lái)顯示表明下一行是這個(gè)語(yǔ)句的繼續(xù)。寫(xiě)過(guò)VB .NET程序的人就應(yīng)該會(huì)感覺(jué)到,這種方法既麻煩,又影響代碼的美觀。
- Dim text As String = "Wouldn't it be nice" & _
 - "If you didn't have to" & _
 - "put an underscore to" & _
 - "continue to the next line?"
 
還好,現(xiàn)在我們?cè)僖膊挥眠@樣了。VB.NET現(xiàn)在支持隱式結(jié)尾續(xù)行(implicit line continuation)。當(dāng)編譯器在某行發(fā)現(xiàn)一條不完整的語(yǔ)句時(shí),它會(huì)自動(dòng)檢查下一行的內(nèi)容是否包含語(yǔ)句的剩余部分。
- Dim text As String = "Wouldn't it be nice" &
 - "If you didn't have to" &
 - "put an underscore to" &
 - "continue to the next line?" &
 - "Sweet! Now you can!"
 
如果你還是喜歡懷舊的感覺(jué),那也還是可以用原先的顯示聲明法,那種方法現(xiàn)在也還是可用的。并且有時(shí)候我們可以會(huì)不得不用它,因?yàn)榫幾g器某些情況下可能無(wú)法判定下一行是不是續(xù)行。放心,這樣的情況是不會(huì)經(jīng)常出現(xiàn)的,而且如果發(fā)生這樣的情況,編譯器會(huì)通知你。
簡(jiǎn)化的屬性語(yǔ)法(VB .NET新引入)
簡(jiǎn)化的屬性語(yǔ)法是另一個(gè)從C#中引入VB .NET的特性。通常屬性定義看起來(lái)是這樣的:
- 'Field
 - Private _name As String
 - 'Property
 - Public Property Name() As String
 - Get
 - Return _name
 - End Get
 - Set(ByVal value As String)
 - _name = value
 - End Set
 - End Property
 
現(xiàn)在可以簡(jiǎn)寫(xiě)成:
- Public Property Name() as String
 
這把代碼行數(shù)從9行減少到1行。如果你選擇了簡(jiǎn)化的這種寫(xiě)法,那要注意的一個(gè)問(wèn)題就是你無(wú)法訪問(wèn)存儲(chǔ)它的值的那塊區(qū)域,這在按引用傳值時(shí)會(huì)帶來(lái)問(wèn)題。如果發(fā)生這種情況,您可以隨時(shí)恢復(fù)到用通常的寫(xiě)法或使用一個(gè)臨時(shí)變量。
數(shù)組類(lèi)型判斷(Array type inference)和多重?cái)?shù)組(Jagged Arrays)(VB .NET新引入)
VB.NET現(xiàn)在支持?jǐn)?shù)組類(lèi)型判斷和多重?cái)?shù)組定義語(yǔ)法。這意味著你在帶初始值定義時(shí)不用顯式地聲明它的類(lèi)型,編譯器能自動(dòng)確定它的類(lèi)型。例如:
- Dim Numbers = {1, 1, 2, 3, 5, 8, 13, 21, 34}
 
當(dāng)你看到這個(gè)數(shù)組時(shí),能很快確定它是整數(shù)型的,現(xiàn)在編譯器就像我們一樣能準(zhǔn)確作出這個(gè)判斷。
- Dim Numbers = {1, 1, 2, 3, 5.0, 8, 13, 21, 34}
 
當(dāng)編譯器看到上面這個(gè)例子時(shí),它會(huì)發(fā)現(xiàn)5.0不是一個(gè)整型數(shù),所以數(shù)組類(lèi)型就是double型。類(lèi)型判斷也可以用于矩陣:
- Dim Names = {{"Sarah", "Jane", "Mary", "Susan", "Amanda"},
 - {"Bob", "Joe", "Dustin", "Richard", "Nick"}}
 
編譯器可以推斷出string()是上面這個(gè)例子的類(lèi)型。在多重?cái)?shù)組中,你會(huì)遇到一些問(wèn)題。你可以把一個(gè)二維矩陣當(dāng)作一個(gè)每行的列數(shù)都相等的矩陣。多重?cái)?shù)組每一行的列數(shù)則是可變的,所以它和矩陣還是有一些區(qū)別。你可能認(rèn)為可以定義這樣一個(gè)多重?cái)?shù)組:
- Dim Names = {"Sarah", "Jane", "Mary", "Susan", "Amanda"},
 - "Bob", "Nick"}
 
但是你會(huì)發(fā)現(xiàn)編譯器拋出錯(cuò)誤說(shuō)”數(shù)組初始化時(shí)缺少三個(gè)元素“(Array initialiser is missing 3 elements), 這是因?yàn)榫幾g器默認(rèn)把它當(dāng)成矩陣看待。如果你想定義一個(gè)多重?cái)?shù)組,那么只需要把這些行用一對(duì)大括號(hào)括起來(lái):
- Dim Names = {{"Sarah", "Jane", "Mary", "Susan", "Amanda"},
 - {"Bob", "Nick"}}
 
現(xiàn)在編譯器就能推斷出來(lái)它的類(lèi)型是string()(),這才是多重?cái)?shù)組的正確類(lèi)型。
From 關(guān)鍵字(VB.NET新引入 )
既然說(shuō)到了初始化,那我們就不得不說(shuō)說(shuō)VB .NET中新引入的From關(guān)鍵字。當(dāng)你創(chuàng)建一個(gè)字典、表格或者其它由許多對(duì)象組成的對(duì)象時(shí),都通常是先創(chuàng)建好這個(gè)對(duì)象本身,然后再用合適的物品去填充它?,F(xiàn)在,有了From關(guān)鍵字,就不用再反復(fù)去調(diào)用Add方法了,它能自動(dòng)幫我們調(diào)用Add方法以填充列表。因此,現(xiàn)在不用像下面這樣寫(xiě)了:
- Dim Colors As New List(Of String)
 - Colors.Add("Red")
 - Colors.Add("Green")
 - Colors.Add("Blue")
 
只用縮減到這么一點(diǎn)就可以了:
- Dim Colors As New List(Of String) From {"Red", "Green", "Blue"}
 
毫無(wú)疑問(wèn),它實(shí)際上也是調(diào)用了Add方法,也就是說(shuō)它能在任何包含Add方法的對(duì)象上起作用。事實(shí)上,你甚至可以使用擴(kuò)展方法(extension method)創(chuàng)建一個(gè)Add方法或重載一個(gè)Add方法,如果傳入的參數(shù)和方法聲明相吻合,F(xiàn)rom關(guān)鍵字就會(huì)用到它們。在前面的示例中,List對(duì)象有一個(gè)只需要一個(gè)參數(shù)的Add方法,參數(shù)值就是你想要加入表格的那個(gè)字符串。如果你有一個(gè)帶多個(gè)參數(shù)的Add方法,那傳入?yún)?shù)時(shí)就可以像定義矩陣一樣做。下面是一個(gè)例子,演示如何使用Add方法和一個(gè)Dictionary對(duì)象。
- Dim Colors2 As New Dictionary(Of String, String) From {
 - {"Red", "FF0000"},
 - {"Green", "00FF00"},
 - {"Blue", "0000FF"}}
 
因?yàn)镈ictionary的Add方法包含兩個(gè)參數(shù),鍵(key)和值(value),我們?cè)贔rom語(yǔ)句里傳入?yún)?shù)時(shí)就必須兩個(gè)兩個(gè)地傳入一組參數(shù)。另外,在使用From關(guān)鍵字時(shí)一定要注意保持可讀性。某些特定情況下,你可能還是會(huì)想回到用Add方法。
【編輯推薦】















 
 
 
 
 
 
 