向VisualBasic程序員介紹泛型
|
本文用通俗的用語和大量的實例向Visual Basic程序員介紹了下一版Visual Basic.Net中將要增加的新功能——泛型。此文章可以幫助廣大VB用戶了解泛型,以便將來將泛型應用到自己的應用程序中。
應用:泛型
此應用展示了在Visual Basic.Net 中新增加的泛型功能。
新概念
在開始實現泛型以前,有必要花一點時間分析一下為什么要在Visual Basic.Net中增加這一功能。泛型技術源于需要用一種一般的方法處理對象各種可能的類型,而不需要關心他們具體的類型。比如在Visual Basic 6.0中,你能夠用一個Collection類儲存任何類型的對象。
Visual Basic 6.0 的集合
Visual Basic 6.0 確實允許你將任何東西儲存在一個Collection中。但是,Collection類有幾個限制。我們用一個例子來說明如何將這個Employee類儲存在集合中:
| ‘ Visual Basic 6.0 代碼:類模塊Employee Public SSN As String Public FirstName As String Public LastName As String Public Salary As Currency |
將這個類儲在集合中的方法顯得非常直接。
| ‘ Visual Basic 6.0 代碼 Dim employees As New Collection Dim emp As Employee Set emp = New Employee emp.SSN = "111-11-1111" emp.FirstName = "Scott" emp.LastName = "Swigart" emp.Salary = 50000 employees.Add emp, emp.SSN |
這段代碼首先創建了一個Collection的實例employees。接著Employee類創建了一個實例,并設置了一些數據。最后,Employee對象被添加到Collection,指定emp.SSN屬性作為關鍵字。下面的代碼展示了如何從Collection取出這個Employee對象的實例:
| ‘ Visual Basic 6.0 代碼 Dim emp2 As Employee Set emp2 = employees("111-11-1111") |
現在我們一起來研究一下Visual Basic 6.0的這種集合有什么限制。首先,你的初衷是讓employees這個集合只儲存Employee類型的對象。但是沒有任何機制可以防止將一個別的類型的對象放入這個employees集合,也沒有任何東西可以告訴你從這個集合中取出的數據是什么類型。下面的代碼照樣可以正確編譯:
| Dim s As String s = employees("111-11-1111") |
雖然開發者可以很明確地知道這不能正確工作,但沒有辦法讓編譯器發現這個問題。這樣會發生一個運行時錯誤。集合的使用同樣限制了智能感知技術的發揮。看看下面這段代碼:
| employees("111-11-1111").LastName = "SomeoneElse" |
這說明你能直接編輯集合中的項目。但是,IDE的智能感知不能幫助你選擇LastName屬性。再一次重申,以前的Visual Basic 中集合可以存放任何東西。
使用集合的兩個最大的限制是性能和靈活性方面的損失。集合雖然容易使用,但作為一個動態數組使用時性能非常差。集合的設計使它更像是一個字典,所以當你需要的數據結構類似堆棧或隊列時,它也不是一個很好的選擇。
框架中的集合
.Net框架1.0/1.1通過增加集合的種類解決了一部分問題。新引入System.Collections命名空間以后,你就可以創建更多類型的集合,比如數組表、位數組、哈希表、隊列、排序表和堆棧。下表列出了這些類型的用法:
| 集合名稱 | 用途 |
| ArrayList | 數組表能夠創建動態增大的數組。 |
| BitArray | 位數組是經過優化的,專用于儲存布爾值(真/假)的數組。 |
| HashTable | 哈希表和Visual Basic 6.0中的Collection類非常相似。它允許你通過關鍵字找得到對應的值。不過關鍵字和值 還是任意類型的。 |
| SortedList | 排序表和哈希表非常類似,唯一不同的是它的關鍵字總是排序的。這意味著當你用For …Each語法遍歷整個集合時,得到的項目總是經過排序的。 |
| Queue | 隊列是一種能讓被儲存的對象先進先出的集合。 |
| Stack | 堆棧是一種能讓被儲存的對象后進先出的集合。 |
.Net 框架1.0/1.1已經解決了Visual Basic 6.0中的集合在靈活性方面的限制,但是,這些集合仍然是弱類型的,因此你還是可以將任何東西存放到一個ArrayList中,雖然在一個指定的應用中,只有儲存唯一的類型才有意義。
你真正想要是指定每個關鍵字必須都是String而且每個值都是Employee類型。在.Net框架1.0和1.1種,你只有創建自己的類,當然這種方法有些繁瑣。如果使用新的.Net框架1.2,這個問題可以用很少的代碼來解決,方法就是使用泛型。
深入代碼
泛型能夠提供嚴格的類型檢查,更好的智能感知功能和更好的性能,就如上一節中介紹的一樣。換句話說,他們超越了以前所有集合類所能提供的優點。
感受泛型
接下來你將看到,當你創建一個泛型集合的實例的時候,你需要提供一些信息以便集合類能夠被強類型化。這樣做有很多優點,包括在編譯階段更多的檢查以確保創建更安全和更可靠的代碼,更好的智能感知以及更好的性能。有必要提及.Net框架1.2中是在以前版本的基礎上新增加泛型集合。.Net框架1.2不會強迫你使用泛型。
如果你想使用泛型類型,首先需要包含System.Collections.Generic這個命名空間。這允許訪問帶有泛型功能的Dictionary、List、Queue、SortedDictionary和Stack類。下面btnConsumeGenerics_Click事件中的代碼提供了一個使用泛型字典的例子:
| ‘ Visual Basic .NET 8.0 代碼 Private Sub btnConsumeGenerics_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConsumeGenerics.Click Dim employees As New Dictionary(Of String, Employee) Dim emp As Employee emp = New Employee emp.SSN = "111-11-1111" emp.FirstName = "Scott" emp.LastName = "Swigart" emp.Salary = 50000 employees.Add(emp.SSN, emp) Dim emp2 As Employee emp2 = employees.Item("111-11-1111") Dim s As String 's = employees.Item("111-11-1111") ' This is now a syntax error employees.Item("111-11-1111").LastName = "SomeoneElse" End Sub |
深入查看這段代碼,你會注意到一些泛型技術中相當有趣的東西。首先泛型的類型是用這樣的方法具體化的:
| Dim employees As New Dictionary(Of String, Employee) |
這可以翻譯成“創建一個Dictionary,它的關鍵字是String類型,值是Employee類型”。任何時候試圖儲存一個不是Employee類型的對象都將導致編譯錯誤。有必要重申,如果使用了泛型,你再用錯類型得到的將是編譯錯誤而不是運行時錯誤。事實上下面這段代碼除非被注釋掉,否則不會通過編譯,就如同編譯器知道Dictionary是專用于儲存Employee對象,而不是String:
| 's = employees.Item("111-11-1111") ' This is now a syntax error |
更進一步,你現在能獲得全面的能感知支持。如果你輸入“employees.Item(“111-11-1111”).”,將自動彈出Employee類型的成員,這說明Visual Studio知道Dictionary現在是專門儲存Employee類的集合。
正如你所見,泛型使用起來很簡單。強類型化的代碼可以避免運行時錯誤;智能感知會工作得更好。雖然使用泛型已經有非常充分的理由,不過使用泛型還有更多的優點:性能和代碼重用。
將泛型技術引入.Net框架的一個主要原因是為了提高性能。比如集合類可以比以前工作得更快,因為編譯器能夠針對集合所儲存的類型進行優化。下面的代碼比較了數組、ArrayList以及泛型List的性能:
| txtOutput.Text = "Performance" & vbCrLf Const iterations As Integer = 5000000 PerfTime.Start() Dim myArray(iterations) As Integer For i As Integer = 0 To iterations - 1 myArray(i) = i Next Dim elapsed As Integer = PerfTime.Stop txtOutput.Text &= "Array time: " & elapsed & vbCrLf myArray = Nothing GC.Collect() PerfTime.Start() Dim myArrayList As New ArrayList For i As Integer = 0 To iterations - 1 myArrayList.Add(i) Next elapsed = PerfTime.Stop txtOutput.Text &= "ArrayList time: " & elapsed & vbCrLf myArrayList = Nothing GC.Collect() PerfTime.Start() Dim myList As New List(Of Integer) For i As Integer = 0 To iterations - 1 myList.Add(i) Next elapsed = PerfTime.Stop txtOutput.Text &= "List time: " & elapsed & vbCrLf myList = Nothing GC.Collect() |
這段代碼在固定長度的數組中儲存了500萬個數值,同時也在自動增長的ArrayList和泛型List中儲存同樣多的數值,性能數值看起來非常有趣:
Array 時間: 344
ArrayList時間: 4656
List時間: 797
有特定類型的定長數組有無與倫比的速度,而且不需要為改變大小付出代價。而集合類型的大小都是自動增長,如果有固定數組1/2的性能是相當不錯的。接下來看看ArrayList,非常不幸,只有固定數據1/10的性能。問題出在ArrayList被設計成儲存引用型變量,Integer是值類型,在儲存到ArrayList以前要經過“裝箱”操作,將Integer轉為Object型。裝箱的代價是非常昂貴的,所以當你儲存值類型數據(如Integer、Date、Boolean以及你自己創建的Structure等)時,使用泛型將獲得非常可觀的性能提升。
更多關于“裝箱”和“拆箱”操作的信息,請參見MSDN庫中的“裝箱轉換”和“拆箱轉換”
創建泛型類型和方法
并不是只能使用Visual Basic.Net提供的泛型類型,你可以創建你自己的泛型類型和方法。
泛型方法
當你想實現一些不與特定類型相關的一般算法時,你可能想創建泛型方法。舉個例子,典型的冒泡排序需要遍歷數組中的所有項目,兩兩比較,并交換需要排序的數值。
如果你已經確定只要進行整數的排序,你可以簡單地編寫一個只能用于Integer類型的Swap方法。但是如果你想能夠排序任何類型,你就可以編寫一個泛型Swap方法如下:
| Private Sub Swap(Of ItemType)(ByRef v1 As ItemType, ByRef v2 As ItemType) Dim temp As ItemType temp = v1 v1 = v2 v2 = temp End Sub |
注意“Of ItemType”,當Swap方法被調用時,除了必須提供所需的參數,還必須傳入一個數據類型。這個數據類型會代替任何實例中的ItemType。下面的例子調用了Swap:
| Swap(Of Integer)(v1, v2) |
這條語句告訴Swap方法它將交換的是Integer類型。如果你回過頭去看看Swap的代碼,這條語句的意思就是讓JIT將所有的ItemType換成Integer,這個Swap方法實際上已經被JIT重寫成:
| Private Sub Swap(ByRef v1 As Integer, ByRef v2 As Integer) Dim temp As Integer temp = v1 v1 = v2 v2 = temp End Sub |
這是實際執行的代碼,JIT生成一個專用于Integer類型的方法。如果你接下來想要排序字符串類型,你就可以用另一Swap的調用如下:
| Swap(Of String)(v1, v2) |
當方法執行的時候,JIT會生成另一個版本的Swap,這次是特定成String類型的:
| Private Sub Swap(ByRef v1 As String, ByRef v2 As String) Dim temp As String temp = v1 v1 = v2 v2 = temp End Sub |
下面是一個使用泛型Swap的冒泡排序的完整例子:
| Private Sub btnSortIntegers_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSortIntegers.Click Dim ints(9) As Integer Dim r As New Random For i As Integer = 0 To 9 ints(i) = r.Next(1, 100) Next ' 冒泡排序 For j As Integer = 0 To 9 For k As Integer = 9 To 1 Step -1 If ints(k) < ints(k - 1) Then Swap(Of Integer)(ints(k), ints(k - 1)) End If Next Next txtOutput.Text = "Sort Integers" & vbCrLf For i As Integer = 0 To 9 txtOutput.Text &= ints(i) & vbCrLf Next End Sub |
泛型類型
最后一點,你能夠創建完全泛型的類型,使用這種“Of ItemType”方法創建類的聲明如下:
| Public Class SomeClass(Of ItemType) Private internalVar as ItemType Public Function SomeMethod(ByVal value As ItemType) As ItemType End Function End Class |
這段代碼對類的作用與方法是相同的。JIT編譯器會簡單地將實例中的ItemType替換成實例化時特別指明的類型。
約束
泛型技術還支持一種叫做約束的特性。這項功能確保在指定類型的時候,傳入的類型最起碼要實現某些功能。比如你要實現一種排序算法,你需要確保傳入的類型能夠實現IComparible接口。你可以用約束來完成這個設想:
| Public Class SomeClass(Of ItemType As IComparible) Public Function SomeMethod(ByVal value As ItemType) As ItemType End Function End Class |
結論
泛型技術相對于以Object為基礎的集合提供了很多好處,首先,泛型類是強類型的,這就確保所有的錯誤在編譯時能夠發現。強類型還可以讓智能感知提供更多方便。泛型還能讓你簡化代碼,讓你的算法可以作用于多種類型。最后,泛型集合要比以Object為基礎的集合快得多,特別是用于值類型時。