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