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