自 1991 年 Visual Basic 語言誕生之日起,它就一直是生成應用程序的高效 率工具。將近 20 年之後,它繼續提供與 Microsoft .NET Framework 的輕松對 接,使開發人員能夠編寫可跨越桌面、電話、浏覽器甚至雲的應用程序。
Microsoft 將在本月發布 Visual Studio 2010,其中包含 Visual Basic 版本 10(有時稱為 VB 2010 或 VB10)。此版本是迄今為止最強大的版本,包含 許多省時省力的功能,可以幫助開發人員通過更少的代碼行完成更多的操作。在 這裡,將會為您提供所有必要的內容,讓您充分了解並利用 Visual Studio 2010 中的 Visual Basic。
共同演變
在過去,Visual Basic 和 C# 是 由獨立團隊分別開發的,這通常會導致一些功能先出現在一種語言中,繼而又出 現在另一種語言中。例如,C# 有 Visual Basic 中所沒有的自動實現屬性和集合 初始值設定項,而 Visual Basic 則有 C# 中所沒有的晚期綁定和可選參數等功 能。但每當一種語言具有新功能時,許多客戶都會要求將該功能也添加到另一種 語言中。
為了解決這一需求,Microsoft 合並了 Visual Basic 和 C# 團 隊,實行共同演變的策略。目的是為推動這些語言共同發展。當一種語言中引入 重大功能時,它也會出現在另一種語言中。這並不是說每種功能都將出現在兩種 語言中,並按完全相同的方式工作;實際上,每種語言都有自己的歷史、靈魂和 感覺 – 保留這些特性非常重要。共同演變意味著您在一種語言中可以執行 的任何任務都可以通過另一種語言輕松實現。
在 .NET Framework 4 中, Visual Basic 和 C# 朝這一目標邁進了一大步,分別吸收了對方既有的許多功能 。然而,共同演變不僅影響到以前的功能;它同樣是這些語言未來的發展策略。 本著這種精神,.NET Framework 4 在兩種語言中同時引入了強大的新功能,例如 動態語言運行時、嵌入式互操作類型和泛型方差,從而使 Visual Basic 和 C# 開發人員能夠充分利用 .NET Framework。
Visual Basic 2010 新增功能
Visual Basic 2010 中的新功能旨在幫助您通過更少的代碼行實現更多操 作。我們 Visual Basic 設計團隊仔細研究了開發人員通常不得不編寫大量繁瑣 樣板代碼的地方,並找到相應解決辦法,讓編譯器代替執行此類工作。當然,這 是從整體上來看,現在就讓我們深入了解各項功能。
隱式行繼續符
Visual Basic 是一種面向行的語言,它使用類似於英語的清晰語法來增 強可讀性。但這通常會導致代碼遇到每行 80 個字符的限制,從而迫使開發人員 要進行大量滾動。您可以使用下劃線字符來告知編譯器應將下一行作為當前行繼 續處理(也就是說,將多個物理行視為單個邏輯行)。但不得不重復地鍵入下劃 線字符一直很令人煩惱,而事實上多年以來排在首位的功能請求就是讓編譯器 “解決這個問題”。
而在 Visual Basic 2010 中,編譯器能 夠解決這個問題。編譯器現在知道哪些標記(例如逗號、圓括號和運算符)往往 出現在行繼續符前面,並且它會插入字符,因此開發人員不再需要插入字符。例 如,用逗號作為 Visual Basic 語句的結尾肯定不合邏輯;編譯器知道這一點, 因此,當編譯器看到諸如 {comma, enter} 這樣的標記流時,它會推斷出存在行 繼續符,如圖 1 中的示例所示。
圖 1 推斷出行繼續符
<Extension()> Function FilterByCountry( ByVal customers As IEnumerable(Of Customer), ByVal country As String) As IEnumerable(Of Customer) Dim query = From c In customers Where c.Country = country Select <Customer> <%= c.Name & "," & c.Country %> </Customer> Return query End Function
在 Visual Basic 2008 中,圖 1 中的代碼將需要 9 個下劃線字符。然而,在以下每種情況下,編譯器會推斷出下劃線字符在何時是 必要的,並允許將其忽略:
在 <Extension()> 屬性之後
在 方法聲明中的 ((左圓括號)之後
在第一個參數的 ,(逗號)之後
在方法聲明中的 )(右圓括號)之前
在 =(等號)之後
在 <%=(嵌入式表達式的開始標記)之後
在 XML 文本的每個 &(與號)之後
在 %>(嵌入式表達式 的結束標記)之前
這個新的編譯器功能對於方法簽名特別有用,它對於所 示示例中超過 80 個字符的情況也將正常工作(如果每一部分都位於同一行上) 。在圖 2 中,您將看到行繼續符為隱式的標記和位置的所有組合。
圖 2 行繼續符為隱式的情況
標記 之前 之後 ,(逗號)、.(句點)、>(屬性)、( {(左括 號)、<%=(嵌入式表達式開始標記(XML 文本)) X )、}、](右括號)、%>(嵌入式表達式結束 標記) X 所有 LINQ 關鍵字:
Aggregate、 Distinct、From、Group By、Group Join、Join、Let、 Order By、Select、Skip、Skip While、Take、Take While、Where、In、Into、 On、Ascending、Descending
X X 運算 符:
+、 -、*、/、、^、>>、<<、Mod、&、+=、-=、*= 、/=、=、^=、>>=、<& lt;=、&=、<、<=、>、 >=、<>、Is、IsNot、Like、And、Or、Xor、 AndAlso、 OrElse
X With(在對象初始值設定項中 ) X如您所見,有 60 多處該 語言不需要下劃線字符的地方。(事實上,本文中的任何一個代碼示例都不需要 行繼續符。)當然,您仍然可以使用下劃線字符,因此 Visual Basic 以前版本 中的代碼將仍然按預期方式編譯。
語句 Lambda
術語 lambda 乍聽 上去可能很嚇人,但 lambda 只是在另一個函數內定義的函數。Visual Basic 2008 引入了帶 Function 關鍵字的 lambda 表達式:
Dim customers As Customer() = ... Array.FindAll(customers, Function(c) c.Country = "Canada")
Lambda 表達式使您能夠在本地以細致緊湊的方式表達邏輯,而不必跨 多個方法拆分邏輯。例如,下面是 Visual Basic 2005(不支持 lambda 表達式 )中以前的代碼的表示形式:
Dim query = Array.FindAll (customers, AddressOf Filter) ... Function Filter(ByVal c As customer) As Boolean Return c.Country = "Canada" End Function
不幸的是,Visual Basic 2008 的 lambda 表達式要求 表達式返回值,因此以下代碼:
Array.ForEach(customers, Function(c) Console.WriteLine(c.Country))
將會導致以下情況:
'Compile error: "Expression does not produce a value."
Console.WriteLine 是一個 Sub 過程(C# 中為 void),因此 它不會返回值,而這就是編譯器產生錯誤的原因所在。為了處理此情況,Visual Basic 2010 引入了對語句 lambda 的支持,後者是包含一個或多個語句的 lambda:
Array.ForEach(customers, Sub(c) Console.WriteLine (c.Country))
由於 Console.WriteLine 不返回值,因此我們可以只創 建 Sub lambda,而不是 Function lambda。下面是使用多個語句的另一個示例:
Array.ForEach(customers, Sub(c) Console.WriteLine("Country Name:") Console.WriteLine(c.Country) End Sub)
當此代碼運行時,它將為每個 客戶打印兩行。另外請注意,如果在編碼時懸停在 c 上,您將看到編譯器會將類 型推斷為 Customer(鍵入 c As Customer 來顯式聲明類型也是合法的)。動態 編寫事件處理程序是語句 lambda 的另一個出色用途:
AddHandler b.Click, Sub(sender As Object, e As EventArgs) MsgBox("Button Clicked") 'insert more complex logic here End Sub
並且,事實上,您可以將語句 lambda 與 Visual Basic 2008 中引入的一項功能(松散委托)結合使用。(可以使用委 托 – 類型安全的函數指針 – 一次性執行多個函數。)這種組合將 生成更為簡單的簽名:
AddHandler b.Click, Sub() MsgBox("Button Clicked") 'insert more complex logic here End Sub
委托松散使您可以完全忽略事件處理程序中的參數 – 這是一個很好的 優點,只要它們根本未使用過,因此它們只會在視覺上帶來干擾。
除了到 目前為止我們已看到的單行 Sub lambda 和多行 Sub lambda 外,Visual Basic 2010 還支持多行 Function lambda:
Dim query = customers.Where(Function(c) 'Return only customers that have not been saved 'insert more complex logic here Return c.ID = -1 End Function)
語句 lambda 的另一個 引人關注的方面是它們與 Visual Basic 2008 引入的匿名委托的相交方式。人們 經常將這些委托與 C# 的匿名方法混淆,盡管嚴格來說它們並不相同。當 Visual Basic 編譯器基於 lambda 的方法簽名推斷委托類型時,將發生匿名委托:
Dim method = Function(product As String) If product = "Paper" Then Return 4.5 'units in stock Else Return 10 '10 of everything else End If End Function MsgBox(method("Paper"))
如果運行此代碼,您將看到消息框中顯示值 4.5。此外,如果懸停在 method 上,您將看到文本 Dim method As <Function(String) As Double>。由於我們未提供實際委托類型,因此編 譯器將自動生成一個委托類型,如下所示:
Delegate Function $compilerGeneratedName$(product As String) As Double
這稱為 匿名委托,因為它只會出現在編譯器生成的代碼中,而不會出現在編寫的代碼中 。請注意,當事實上沒有提供 As 子句來指定 lambda 的返回類型時,編譯器將 返回類型推斷為 Double。編譯器將查看 lambda 內的所有返回語句,並將確定類 型 Double (4.5) 和 Integer (10):
'Notice the "As Single" Dim method = Function(product As String) As Single If product = "Paper" Then Return 4.5 'units in stock Else Return 10 '10 of everything else End If End Function
然後,它將運行其基准類型算法,並確 定它能夠安全地將 10 轉換為 Double,但無法安全地將 4.5 轉換為 Integer; 因此 Double 是更好的選擇。
您也可以顯式控制返回類型,在這種情況下 ,編譯器將不會嘗試推斷類型。非常常見的做法是將 lambda 賦給具有顯式委托 類型的變量,而不是依賴於編譯器來推斷委托類型:
Dim method As Func(Of String, Single) = Function(product) If product = "Paper" Then Return 4.5 'units in stock Else Return 10 '10 of everything else End If End Function
由於提供了顯式目標類型,因此無需聲明 As String 或 As Single;編譯器可基於語句左邊的委托類型來推斷出其存在。因此,如果 您懸停在 product 上,將會發現推斷出的類型為 String。不再必須指定 As Single,因為委托類型已提供該信息。在前面的示例中,Func 委托(.NET Framework 包括該委托)的簽名如下所示:
Delegate Function Func(Of T, R)(ByVal param As T) As R
但有一個很小的例外 之處,稍後我們將在“泛型方差”一節中看到。
自動實現的屬 性
在 Visual Basic 中,屬性是用於向外部公開對象狀態的類成員。典型 的屬性聲明與如下聲明類似:
Private _Country As String Property Country As String Get Return _Country End Get Set(ByVal value As String) _Country = value End Set End Property
一個實際上非常簡單的概念就有 10 行代碼。由於典型 的對象通常有數十個屬性,因此您最終會在類定義中包括大量樣板代碼。為了簡 化此類任務,Visual Basic 2010 引入了自動實現的屬性,利用該屬性,您只需 使用一行代碼即可定義簡單的屬性:
Property Country As String
在這種情況下,編譯器將繼續運 行並自動生成 Getter、Setter 和支持字段。支持字段的名稱是始終為前面帶有 下劃線字符的屬性的名稱:此例中為 _Country。這種命名約定在將自動實現的屬 性更改為常規屬性的情況下確保了二進制序列化兼容性。只要支持字段的名稱相 同,二進制序列化就將繼續工作。
您可使用自動實現的屬性執行的其中一 項出色的功能是:指定在構造函數運行時設置屬性默認值的初始值設定項。舉例 來說,一個帶有實體類的常見方案將主鍵設置為類似於 -1 的值,以指示其處於 未保存的狀態。代碼將如下所示:
Property ID As Integer = - 1
當構造函數運行時,支持字段 (_ID) 將自動設置為值 -1。初始值設 定項語法也適用於引用類型:
Property OrderList As List(Of Order) = New List(Of Order)
由於無需輸入兩次類型的名稱,因 此上一行代碼可能不會具有非常明顯的“Visual Basic 特征”。好消 息是,常規變量聲明中有一個與 Visual Basic 所允許語法一致的更短的語法:
Property OrderList As New List(Of Order)
您甚至能 夠將此語法與對象初始值設定項結合使用,以允許設置其他屬性:
Property OrderList As New List(Of Order) With {.Capacity = 100}
很顯然,對於更復雜的屬性,擴展的語法仍然是 必要的。您仍然可以鍵入 Property{Tab} 來激活舊屬性片段。或者,在鍵入屬性 的第一行後,您可以只輸入 Get{Enter},IDE 將生成舊樣式的屬性:
Property Name As String Get End Get Set(ByVal value As String) End Set End Property
人們通常會發現:新的屬性語法與公共字段的語法幾乎 相同,那麼為什麼不改為使用公共字段?有幾個原因:
大多數 .NET 數據 綁定基礎結構都依據屬性(而不是字段)工作。
接口無法強制要求存在字 段;但可以強制要求存在屬性。
屬性為更改業務規則提供了更長期的靈活 性。例如,假定某人引入了電話號碼必須為 10 位數的規則。如果分配給公共字 段,將無法執行此驗證。對於諸如二進制序列化和反射等方案而言,將公共字段 更改為屬性是一項重大更改。
集合初始值設定項
一種常見 .NET 做法是實例化集合,然後通過為每個元素調用一次 Add 方法來填充該集合:
Dim digits As New List(Of Integer) digits.Add(0) digits.Add(1) digits.Add(2) digits.Add(3) digits.Add(4) digits.Add(5) digits.Add(6) digits.Add(7) digits.Add(8) digits.Add(9)
但對於從根本上而言非常簡單的概念來說,將會產生大 量語法開銷。Visual Basic 2010 引入了集合初始值設定項,使您能夠更輕松地 實例化集合。對於此代碼:
Dim digits = New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
編譯器將自動生 成對 Add 方法的所有調用。您也可以使用 Visual Basic 的 As New 語法的功能 :
Dim digits As New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
請注意,在 Visual Basic Team 上,我 們一直建議使用第二種語法 (As New),而不是前者,因為它使代碼更能適應 Option Infer 設置的更改。
您可以依據滿足以下要求的任何類型使用集 合初始值設定項:
您可以使用 For Each 語句循環訪問該類型 – 也就是說,該類型實現 IEnumerable。(有關集合類型更精確/詳細的定義,請參 見 msdn.microsoft.com/library/aa711986(VS.71).aspx 上 Visual Basic 語言 規范的第 10.9.3 節)。
該類型具有可訪問的(但不一定是公共)無參數 構造函數。
該類型具有可訪問的(但不一定是公共)實例或名為 Add 的 擴展方法。
這意味著,您也可以將集合初始值設定項用於更復雜的類型, 例如字典:
Dim lookupTable As New Dictionary(Of Integer, String) From {{1, "One"}, {2, "Two"}, {3, "Three"}, {4, "Four"}}
(請注意,即使此語句跨了五行,也沒有下劃線字 符。)在這種情況下,編譯器將生成與初始化字典的舊方法等效的代碼:
Dim lookupTable As New Dictionary(Of Integer, String) lookupTable.Add(1, "One") lookupTable.Add(2, "Two") lookupTable.Add(3, "Three") lookupTable.Add(4, "Four")
編譯器在調用具有兩個 參數(而不是一 個參數)的 Add 方法。它之所以知道這樣做,原因是傳入集合初始值設定項的值 位於嵌套的大括號中,如下所示:{{1, “One”}, {2, “Two”}, …}。對於每一組嵌套的大括號,編譯器會嘗試將這 些參數傳遞到兼容的 Add 方法。
也可以通過使用擴展方法來提供您自己 的自定義 Add 實現:
<Extension()> Sub Add(ByVal source As IList(Of Customer), ByVal id As Integer, ByVal name As String, ByVal city As String) source.Add(New Customer With { .ID = id, .Name = name, .City = city }) End Sub
(看看所有這些缺失的下劃線字符!)此方法擴展任何實現 IList(Of Customer) 的類型,然後允許您使用新的集合初始值設定項語法,如下 所示:
Dim list = New List(Of Customer) From { {1, "Jon", "Redmond"}, {2, "Bob", "Seattle"}, {3, "Sally", "Toronto"} }
(向列表 中添加三個客戶)。您也可以將集合初始值設 定項與自動實現的屬性結合使用:
Property States As New List (Of String) From {"AL", "AK", "AR", "AZ", ...}
數組文本
除了更強大的集合類型處理方式外,Visual Basic 2010 還提供了一些用 於處理數組的強大增強功能。假設有以下代碼(在較舊版本中可正常工作):
Dim numbers As Integer() = New Integer() {1, 2, 3, 4, 5}
通過查看該數組中的元素,很明顯每個元素都是整數,因此,必 須實際上在此行中打印輸出兩次 Integer 的操作不會真正添加任何值。數組文本 允許將某個數組的所有元素放在大括號內,然後讓編譯器自動推斷類型,從而創 建該數組:
Dim numbers = {1, 2, 3, 4, 5}
numbers 的類型不是 Object,而是 Integer()(只要啟用了“Option Infer” ),原因是數組文本現在可代表本身,並且有其自己的類型。假設有一個更復雜 的示例:
Dim numbers = {1, 2, 3, 4, 5.555}
在這 種情況下,numbers 的類型將被推斷為 Double()。編譯器通過檢查數組中的每個 元素並計算基准類型(所使用的算法與前面討論的用於推斷語句 lambda 的返回 類型的算法相同),從而確定類型。如果沒有基准類型將會發生什麼情況?例如 ,以下代碼中所示:
Dim numbers = {1, 2, 3, 4, "5"}
在這種情況下,將 Integer 轉換為 String 將會縮小轉換范圍(也就 是說,在運行時可能會出現數據丟失的情況),同樣,將 String 轉換為 Integer 也會縮小轉換范圍。可選擇的唯一安全的類型為 Object()(如果啟用了 “Option Strict”,編譯器將產生錯誤)。
可以嵌套數組文 本以形成多維數組或交錯數組:
'2-dimensional array Dim matrix = {{1, 0}, {0, 1}} 'jagged array - the parentheses force evaluation of the inner array first Dim jagged = { ({1, 0}), ({0, 1}) }
動態語言運行時
盡管 Visual Basic 從技術上而言實質上是靜態語言,但它一直有非常強 大的動態功能,例如晚期綁定。Visual Studio 2010 附帶了一個名為動態語言運 行時 (DLR) 的新平台,利用該平台可更為輕松地生成動態語言 – 並在這 些語言之間通信。Visual Basic 2010 已更新為在其晚期綁定程序中完全支持 DLR,從而使開發人員能夠使用采用其他語言(例如 IronPython/IronRuby)開發 的庫和框架。
此功能的一項突出優點是,從語法上而言沒有任何內容發生更改(事實上,在 編譯器中沒有修改任何一行代碼來支持此功能)。開發人員仍然能夠像在 Visual Basic 以前的版本中一樣進行晚期綁定的操作。發生變化的是 Visual Basic 運 行庫 (Microsoft.VisualBasic.dll) 中的代碼,該運行庫現在可識別 DLR 提供 的 IDynamicMetaObjectProvider 接口。如果某個對象實現此接口,則 Visual Basic 運行庫將構建 DLR CallSite,並允許該對象及提供該對象的語言將它們自 己的語義注入操作。