Visual Basic 14 的 14 大新特性

InfoQ - 邵思華發表於2015-02-05

與Visual Studio類似,Visual Basic也將從版本12直接跳到14。雖然新版本中的許多特性對於C#來說也是首次引進,但仍然有大量的功能增強是特別針對VB的,旨在簡化VB的使用。本文列舉了一些最令我們感興趣的特性。

對Null的支援

新版本的一個特性是對null值的支援,該特性使用?.操作符。這一特性與C#相同,如果操作符左方的表示式返回值不為null,則繼續計算右方表示式。在處理外部資源所返回的少量資料時,該特性尤其有用。舉例來說:

If customer.PrimaryResidence IsNot Nothing AndAlso customer.PrimaryResidence.Garage IsNot Nothing AndAlso property.PrimaryResidence.Garage.Cars = 2 Then Print(“Two Car Garage”)

這段程式碼將被簡化成以下語句:

If property.PrimaryResidence?.Garage?.Cars = 2 Then Print("Two Car Garage")

除此之外,還可以將該操作符與If操作符進行結合,實現為表示式提供某個預設值的功能:

Dim numberOfCarPorts = If(property.PrimaryResidence?.Garage?.Cars, 0)

C#與VB並不是唯一兩種支援這種null處理方式的語言。在Apple的產品中得到廣泛應用的Objective-C語言預設就支援該行為。尤其是它的方法呼叫也使用.操作符,其工作方式就類似於VB中的?.操作符。

在Objective-C的社群中,人們對該特性的評價褒貶不一。某些開發者非常喜愛這項功能,因為他們在進行方法呼叫時無需擔心空引用異常的產生。而另一些開發者則對此感到痛恨,因為在問題發生時,他們不會看到空引用異常的產生,只會看到方法呼叫失敗。如此一來他們就會感到困惑,為什麼方法返回了null,而不是返回有效值或丟擲異常。

超程式設計

在Visual Basic 12中,我們首次看到了CallerNameAttribute這一特性的引入。雖然這一特性解決了屬性變更通知(property change notification)的問題,但它的通用性還不足以解決另外一部分問題,在這些問題中需要一個以字串形式表達的唯一識別符號,在這種情況下,需要使用到NameOf這個操作符。

以下這個示例是由來自Visual Basic團隊的Lucian Wischik所提供的,其中包括對引數進行驗證的邏輯。

Function Lookup(name As String) As Customer

If name is Nothing Then Throw New ArgumentNullException(NameOf(name))

這種方式能夠避免在修改了引數名稱的時候,忘記修改了所丟擲異常的建構函式中所定義的字串。由於NameOf操作符實際上建立了一個常量,因此你可以在任何需要使用硬編碼字串的時候使用該操作符。

字串插值(Interpolation)

自從十年以前.NET初次問世的時候,String.Format這個方法就要求開發者們對引數的數量進行計數。多年以來,由於計數錯誤所產生的bug可謂是不計其數。字串插值這一技術最初是由Mono團隊為C#語言所建立的,它徹底解決了計數這種糟糕的做法。

插值字串是由$”開頭的,而不是單單使用”。對於每個你需要插入值的位置,都要使用一對大括號進行轉義,這一點與String.Format的做法是相同的。另一個與String.Format相同的地方在於可以在轉義中加入格式化選項。在下面這個簡單的示例中出現了兩個變數,name和total,後者將被格式化為貨幣格式。

Dim message = $"Hello {name}, your amount due is {total:C2}"

該語法本身就使用了String.Format方法,因此使用者同樣需要注意在適當的場合進行轉義,考慮一下以下字串:

Dim requestUrl = $"http://{server}/customer?name={customerName}"

這段程式碼會產生一個bug,開發者實際上需要的是以下程式碼:

Dim requestUrl = $"http://{server}/customer?name={UrlEncode(customerName)}"

FormattedString物件

乍一看插值字串的語法,似乎無法處理從外部資源中獲取字串的場景,例如從本地化表或資源字典中獲取字串。不過,微軟正在努力實現這一功能。Lucian Wischik寫道:

不僅能夠使用在不同的語言文化中,而且還能夠從中抽取出原始的格式化字串或者是引數(舉例來說,如果你打算在SQL查詢中使用該語法,或者會需要對引數進行轉義,以避免產生字串注入攻擊)。但目前為止,我們還沒有完全決定該語法的設計規格。

按照當前的規格宣告草稿所說,插值字串可以是一個常規的字串,也可以由一個名為FormattedString的物件實現。當你試圖將某個插值字串賦值給一個實現了IFormattable介面的變數或是引數時,系統會自動建立一個FormattedString型別的例項。

該物件的IFormattable.ToString方法接受一個型別為IFormatProvider型別的引數,使用該引數能夠重寫格式化相關的行為。

string IFormattable.ToString(string ignored, IFormatProvider formatProvider)
        {
            return String.Format(formatProvider, format, args);
        }

在上面一段程式碼中,format與args兩個引數分別代表了待插值的原始字串,以及它所對應的值。

多行字串

在VB中新加入的一個特性是多行字串。實現它不需要任何特殊的語法,只需要在希望分行的地方省略引號即可。根據原始碼檔案所使用的換行符的不同,該換行符會自動在vbCrlf、vbCr及vbLf等符號間進行選擇。對於Visual Studio的使用者來說,基本上都會選擇vbCrlf。

在目前,某些開發者會選擇在XML文字中使用CData段落來模仿這一特性,這種方式雖然能夠實現所需要的效果,但顯得有些冗長與笨拙。

屬性

自動屬性現在可以標記為只讀了。可以在宣告時為該屬性賦值,也可在建構函式中進行賦值。

該語法的使用方式應該不會出乎你的意料:

Public ReadOnly FirstName As String = "Anonymous"
Public ReadOnly LastName As String
Public Sub New (firstName As String, lastName As String)
    Me.FirstName = firstName
    Me.LastName = lastName
End Sub

在使用這一特性時,應當考慮到某些特殊情況。要理解這些情況,你首先必須理解引數傳遞的copy-in和copy-out概念。CLR只允許你為變數及欄位進行引用傳遞(即C#中的ref或out操作符)。但在VB中,你也能夠為屬性進行引用傳遞。

為了緩解這兩者之間的分歧,VB會在準備進行函式呼叫時建立一個本地變數,該屬性的值會被拷貝到這個本地變數中。該本地變數隨後被傳遞至函式中,函式體能夠修改該本地變數的值。當該函式返回時,本地變數的值會拷貝回屬性中。

在使用只讀的自動屬性時,將會應用以下規則:

  1. 如果你在建構函式中的某個lambda表示式中使用只讀自動屬性,編譯器會提示語法錯誤。
  2. 如果在建構函式或初始化器中使用只讀自動屬性,將應用copy-in與copy-out規則。Copy-out操作會將值寫入系統為屬性生成的欄位中。
  3. 如果不在建構函式或初始化器中使用只讀自動屬性,則只會應用copy-in規則。Copy-out操作根本不會發生,但也不會產生任何語法錯誤。

這些規則都是基於只讀欄位的工作原理所產生的。

註釋

現在,在一個多行語句的每一行末尾都可以加入註釋了。在之前的版本中,只能在多行語句的最後一行末尾加入註釋。請看以下示例:

Dim emailList = 
    From c in Customers
    Where c.IsActive 'ignore inactive customers
    And Not c.DoNotEmail 'we don’t need another spam violation
    Select c.FullName, c.EmailAddress

結構體

結構體現在能夠支援無參建構函式了。雖然CLR本身就支援這一特性,但還沒有主流的程式語言實現了這一特性,其原因是建構函式的執行時機並不明確。舉例來說,在建立某個結構體的陣列時,該結構體的建構函式並不會執行。

如果你的程式碼是myStruct = new MyStructure(),那很顯然該建構函式會立即執行。而如果你的程式碼是myStruct = Nothing,則顯然不會執行建構函式。但在某個本地變數或成員變數自動初始化時又是否會執行建構函式呢?無論你選擇哪一種答案,總會讓一部分人感覺不爽。

資料文字(Data Literals)

從今年開始,資料文字(對於JSON格式來說非常重要的一個特性)終於改為使用符合ISO標準的格式了。在過去,資料文字一直使用基於美國的格式化形式,對於居住在歐洲的人來說就會產生一些迷惑。

  • 老風格:#3/4/2005#(是三月四日,還是四月三日?)
  • 新風格:#2005-4-3#

與C#的互操作性

Overrides修飾符將會隱含使用Overloads修飾符。在過去,VB的開發者必須同時使用這兩種修飾符,才能保證C#的使用者在使用由VB所建立的類庫時能夠呼叫正確的過載方法。

介面模糊性

在C#中使用介面繼承這一特性時,會造成不易判斷到底是哪個介面方法被呼叫的問題。在VB中不允許出現這種場景,但由於C#允許這一特性,會造成出現某些VB無法實現的介面的情況。(在Microsoft Dynamics的某個產品中就數次出現這種情況。)

相對於C#中所使用的“通過名稱隱藏”(hide-by-name)的過載規則,VB 14中將對這一限制進行放寬,轉而使用一種(對VB來說)更傳統的方式,即“通過簽名隱藏”的規則。

名稱空間解析

VB也曾在名稱空間解析這一問題上栽過跟斗,考慮一下以下程式碼:

Threading.Thread.Sleep(1000)

按Lucian Wischik所說:

之前,VB會嘗試查詢“Threading”這一名稱空間,由於它無法分辨System.Threading和System.Windows.Threading的區別,因此直接報錯。現在,VB14會同時支援這兩種可能匹配的名稱空間。如果你在程式碼編輯器輸入Threading.,那麼在輸入.號之後,你會在智慧提示中看到對這兩個名稱空間的支援。

類似的情況還有許多,舉例來說:在編寫Winforms應用時,ComponentModel.INotifyPropertyChanged事件就會無法分辨System.ComponentModel及System.Windows.Forms.ComponentModel,這一問題如今將不復存在。

TypeOf和IsNot

微軟在十年前就建立了IsNot操作符,自那以來,就不斷有VB的開發者要求微軟允許在TypeOf表示式中使用IsNot操作符,舉例如下:

If TypeOf sender IsNot Button Then

預處理指令

VB 14為預處理指令提供了兩點改進之處。

Regi7on

Region將能夠在函式體中進行使用,甚至是跨兩個函式體進行使用。

關閉警告

與C#相同,Visual Basic現在也能夠關閉對某一個程式碼塊的編譯警告了。在規格說明中提供了一個示例:

#Disable Warning BC42356 'suppress warning about no awaits in this method

通常來說,開發者會通過某個指令在該程式碼檔案的其它地方重新開啟這一警告

#Enable Warning BC42356

如果該警告的ID中包含了空格或標點符號,則必須使用引號。微軟的工具不會自動為你完成這一點,不過由Roslyn所編寫的第三方分析器規則或許能實現這一點。

VB的快速修復(Quick Fix)特效能夠通過自動新增這些指令實現繞過某些警告的目的。這一點對於之前提到的第三方分析器規則來說尤其有用,因為你不一定能夠很快地找到對應的ID。

XML文件驗證

目前來說,VB編譯器會忽略XML文件的內容。而在VB 14中,編譯器就會試圖在文件中查詢錯誤,例如不正確的引數引用名稱。它還能夠“正確地處理crefs標籤中的泛型與操作符”。

部分模組(partial module)與介面宣告

與類和結構體型別,你現在能夠將模組與介面宣告為部分(partial)了。通常來說,這一特性是為程式碼生成器所準備的,但也能夠在跨多個平臺分享程式碼時發揮作用。

相關文章