1. 程式人生 > >VB.net學習筆記(十九)陣列、集合、泛型

VB.net學習筆記(十九)陣列、集合、泛型

變數、陣列、集合、泛型的發展

           最開始用記憶體中一個位置對映一個值,用變數來“使用”這個值。

           進一步發展,用變數來引用一組值,這就是陣列。由陣列概念,發展出連結串列、堆、棧,進行排序、檢索等。

           但這並不能完全表達,由此發展出集合概念,它是更強大的陣列,都依賴於Object基類。

           隨之而來的是集合中元素並不一定是一樣的,於是都得轉為Object,涉及到裝箱,使效能下降,特別是元素量巨大時。而且

                          由於我們一般使用同一型別(強型別)更方便操作。由此產生了泛型

           泛型簡單地說,就是把裡面的元素強制指定為特定的型別,也可以說是模板。

一、陣列

1、陣列的定義      

         System.Array類是陣列的基礎,陣列就是由它派生而來。

         所有.Net陣列和集合的下標總是從0開始。故元素的個數是上限+1

         陣列的定義:

        Dim a1(20) As Integer
        Dim a2() As Integer = {1, 2, 3, 4}
        Dim a3(4, 2) As Integer
        Dim a4(,) As Integer = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}, {13, 14, 15}}
        Dim a5() As Integer

        a1陣列是從a1(0)到a1(20)共21個元素,而不是20個元素。20表示上限。

        a2表示4個元素,上限是3,即a2(3)

        a3表示的是二維陣列

        a4也是二維,但其一維和二維的上限由後面的值來確定。

        a5表示是不定陣列,將在後面的使用中來確定,它是一個重要的概念

2、多維陣列

       UBound用來提取陣列中某維中的上限(注意不是個數),LBound是提取陣列的下限,由於下限永遠是從0開始,所以這個函式沒用了。

        另外維數是從左到右,從1開始計數(這與元素索引從0開始計數不同)

        需要注意的是: GetUpperBound(0)也是提取上限,例如 :  a1.GetUpperBound(0)

                                     它的維數卻是以0開始,相當於UBound中維數的1

Module Module1

    Sub Main()
        Dim a(,) As Int32 = {{1, 2, 3}, {3, 4, 5}, {6, 7, 8}}
        Dim temp As Int32
        For i As Int32 = 0 To UBound(a)  '即UBound(a,1)
            For j As Int32 = 0 To UBound(a, 2) '將返回其最大可用下標的維度。 對第一維使用 1,對第二維使用 2,
                temp += a(i, j)                '依此類推。 如果省略 Rank,則假定為 1。
            Next
        Next
        Console.WriteLine(temp)
        Console.Read()
    End Sub

End Module


3、不定陣列

       就是宣告時並沒有例項化的陣列,它只是說明了型別,卻沒有在記憶體中分配空間(因為元素個數未定)

       因此,它沒有具體例項化前是不能直接使用的,如圖,出錯:

       

       未處理,其值為空。

        ReDim

         不定陣列在使用前須用ReDim來例項化(指明個數,以便分配記憶體空間),但不得改變成其它型別,否則出錯。

Sub Main()
        Dim a() As Int32
        ReDim a(3) '只能例項化,不能宣告(成型別)
        Console.WriteLine(a(0))
        Console.Read()
End Sub

        注意:ReDim與VB6中不同:(1)須先宣告型別,再用ReDim,不能用ReDim來宣告(成其它型別)

                                                              (2)不能改變陣列維數(增加或減少都不行)

        

         Preserve

         保持之意。不定陣列經ReDim例項化後,還可再次用ReDim來改變,第二次改變會直接改變第一次例項化中重疊的元素。

          為了保持元素值,用Preserve來指明。

          注意Preserve只能修改最後一維的大小。

    Sub Main()
        Dim a(,) As Int32
        ReDim a(6, 5)
        a(6, 1) = 3
        ReDim a(7, 4)        '正確,無Preserve時,可以修改多維 
        'ReDim Preserve a(5, 4) '錯誤,有Preserve時,只能修改最後一維的大小
        Console.WriteLine(a(6, 1))
        Console.Read()
    End Sub


二、集合

             陣列功能很強大,但Array基類並沒為陣列提供更多的功能,比如排序、動態分配記憶體。為了更強大的功能產生了集合。

            集合(Collections)名稱空間是System名稱空間的一部分,它提供系列高階功能。

            對不同的用處,System.Collections名稱空間提供了幾個強大的類:

                      ArrayList    實現一個數組,其大小在新增元素時自動增加大小(不必煩惱陣列的上限或用ReDim、Preserve)

                      BitArray      管理以位值儲存的布林陣列

                      Hashtable   實現由鍵組織的值的集合(Key,Value),排序是基於鍵的雜湊完成的(雜湊函式)

                      Queue         實現先進先出集合(排序方式)

                      Stack            實現後進先出集合

                      SortedList    實現帶有相關的鍵的值的集合,該值按鍵來排序,可以通過鍵或索引來訪問

1、ArrayList  陣列列表

             ArrayList 僅一維且不保證是排序的。 可使用一個整數索引訪問此集合中的元素。 此集合中的索引從零開始。

             在執行需要對 ArrayList 排序的操作(如 BinarySearch)之前,必須對 ArrayList 進行排序。

           ArrayList 的容量是 ArrayList 可以包含的元素數。 隨著向 ArrayList 中新增元素,容量通過重新分配按需自動增加。 
           可通過呼叫 TrimToSize 或通過顯式設定 Capacity 屬性減少容量。對於非常大 ArrayList 物件,則在執行時環境
          (ide) 中增加最大容量為 20億在 64 位系統的元素通過設定 gcAllowVeryLargeObjects 配置元素的 enabled 屬性設定為 true 。

            ArrayList 集合接受 null 引用(在 Visual Basic 中為 Nothing) 作為有效值並且允許重複的元素。

Module Module1

    Sub Main()
        '使用大小會根據需要動態增加的陣列來實現 IList 介面
        Dim objArryList As New System.Collections.ArrayList
        Dim objItem As Object
        Dim intLine As Int32 = 1
        Dim strHello As String = "Hello"
        Dim objWorld As New System.Text.StringBuilder("World")

        objArryList.Add(intLine)
        objArryList.Add(strHello)
        objArryList.Add(" "c)
        objArryList.Add(objWorld)
        objArryList.Insert(1, ". ") '在索引1處插入。(索引從0開始)

        For Each objItem In objArryList
            Console.WriteLine(objItem.ToString)
        Next
        Console.Read()
    End Sub

End Module

          可以看到使用很方便:1、不需要宣告陣列大小

                                                   2、不需要重寫定義陣列大小

                                                   3、不需要用Preserve來保持資料

          ArrayList都會自動完成這樣的功能。

2、Hashtable  雜湊表

         表示根據鍵的雜湊程式碼進行組織的鍵/值對的集合。

         鍵通過一個雜湊函式來確定元素值的具體儲存位置。這樣就可以快速由Key取得值。

         鍵不能是Nothing(NULL),值可以是。

         優點:定位查詢一個值,插入、刪除一個映像的效率最高。

3、SortedList  排序列表
             hashtable是沒有排序的,所以新增元素會比較快。而SortedList 儲存的鍵值對,是按key進行排序了的,

            因為要排序,所以新增元素時,要先查詢元素的位置再插入,相對慢些,但是在查詢時比較快。

            下面,每變動一次元素,自動會按Key進行排序,所以最後不需排序,就可得到排序的結果:

             

4、Queue 佇列

           表示物件的先進先出集合。

          佇列在按接收順序儲存訊息方面非常有用,以便於進行順序處理。 此類將佇列作為迴圈陣列實現。 儲存在 Queue 中的物件在一端插入,從另一端移除。

          Queue 的容量是 Queue 可以包含的元素數。 隨著向 Queue 中新增元素,容量通過重新分配按需自動增加。 可通過呼叫 TrimToSize 來減少容量。

          等比因子是當需要更大容量時當前容量要乘以的數字。 在構造 Queue 時確定增長因子。 預設增長因子為 2.0。 Queue 的容量將始終加 4,

          無論增長因子是多少。 例如,當需要更大的容量時,增長因子為 1.0 的 Queue 的容量將始終增加四倍。

          Queue 接受 null 引用(在 Visual Basic 中為 Nothing) 作為有效值並且允許重複的元素

          

5、Stack 棧

        表示物件的簡單後進先出 (LIFO) 非泛型集合

          

         注意:比較與Queue的輸出順序

6、迴圈控制

      (1)For...Next...

           For Each...Next...

                     越過迴圈:Contunue For

                     退出迴圈:Exit For

       (2) While..End While       (更常用)

              Do While...Loop

               Do  Until...Loop

                        同理,越過:Continue While        或Continue Do

                                    退出:Exit While                  或Exit  While

       (3)Thread.Sleep()

             大迴圈或無限迴圈中,會一直佔用執行緒,給造成介面假死現象,可用Thread.Sleep(),這樣僅在給定時間才執行,以避免

              消耗過多的處理器時間。

                     while   i=1

                                 ....

                                 system.Threading.Thread.Sleep(500)  

                                 ...

                      end while

三、泛型

1、裝箱(Boxing)

         (1)什麼是裝箱?

                  值型別儲存在棧上,引用型別儲存在堆上。

                  當值型別向引用型別轉變,即從棧向堆上轉移,這時值就變成了一物件,就好像值型別外面包裝了一層東西,這個過程叫裝箱(Boxing)  

        '不需要裝箱,都是值型別
        Dim a1(20) As Integer
        Dim a2 As Integer = 1

        a1(0) = 0
        a1(1) = a2

        '需要裝箱,String是引用型別
        Dim b1 As New System.Text.StringBuilder()
        Dim b2 As New System.Collections.SortedList()
        Dim i As Integer

        For i = 1 To 100
            b1.Append(i) '未裝箱,直接接收
            b2.Add(i, i) '裝箱,引數需要兩個物件,要轉換integer為物件
        Next

           (2)裝箱的影響

                    顯然裝箱會使“值”的外層多了一些“無用”的東西,會使得效能稍有下降。

                    集合中元素都來自Object(引用型別),即,它是在堆上,都涉及到一個裝箱,如果資料量大時,效能下降得就可觀了。

                   當需要時其中的“值”時,又需要把箱子,從堆上轉移到棧上,即引用型別變成值型別,這個過程叫拆箱。

             (3)為什麼要有泛型?

                     集合中,任何引用或值型別都將隱式地向上強制轉換為Object。如果項是值型別,新增到列表中時,進行裝箱操作,在檢索時進行取消裝箱操作。

                     這樣,強制轉換以及裝箱和拆箱操作都會降低效能。

                     另一個限制是缺少編譯時型別檢查。同一個集合可接收不同型別,所有項都強制轉換為 Object,在編譯時無法檢查是否可以收這種型別,還是人為

                     錯誤輸入了另一個型別,同時智慧感應只會提示Object的方式,使得檢查錯誤變得艱難。


                    如果我們對其中的型別進行一些限制,使之成為統一的型別,雖然稍微增加了編碼的複雜性,但好處是可以建立一個 更安全並且速度更快的列表,

                    校驗錯誤也變得容易。

                     鑑於這種情況,催生了泛型的產生。

2、泛型的使用

         泛型主要的目的是建立強型別化的集合,使處理速度加快。所以前面使用Object的普通集合類,最好使用泛型。

         泛型內置於.net中,允許定義程式碼模板,然後使用這個模板宣告變數,它實際上是建立了一個新的資料型別。

         .net基類庫(BCL)裡有許多泛型模板,多位於System.Collections.Generic名稱空間,也有分佈在其它BCL中。

          泛型帶來的效能提升,可以讓任何使用集合資料型別的地方都應當使用泛型代替。

           泛型通常使用List(of   T)形式,List是型別(或者類)的名稱 ,字母T是點位符類似於引數。它表示 必須提供一個用來定製泛型

           的特定型別值,同時也限定的它只能是這個型別。

        Dim data1 As New List(Of Date) '元素只能是Date型別
        '===================================
        Dim data2 As New ArrayList  '未限定元素型別,任意。(object)
        data2.Add(5)
        data2.Add("xxx")
        data2.Add(3.2)
        For Each i As Object In data2
            TextBox1.AppendText(i.ToString & Environment.NewLine)
        Next

             上面可以看到,當沒有限定時,它是Object,因為可能是integer,String,double等,最終將轉向Object。也就是說

              普通集合中元素是多種情況,只有當用了泛型才進行了統一,這樣處理更快。

               當用了泛型後,型別引數指明後,將不能再用其它型別,如下:

        Dim data1 As New List(Of Integer) '元素只能是Integer型別
        data1.Add(33)           '正確
        data1.Add("Hello")      '錯誤,不能為string
               

               泛型有兩種形式:泛型型別和泛型方法

                        List(of  T)是泛型型別,定義了完整的型別或類的模板。

                        泛型 方法是一種方法模板,使用時必須指明方法使用的“具體型別”。

3、Nullabel   可空型別

        簡言之:可以有空值的型別。 比如資料庫有欄位有integer型,但有時是DBNULL(空值),在取時會出錯,這個型別就有用處了。

        Nullable不是值型別。

        Dim intValue1 As New Nullable(Of Integer) '可為空的Integer型別
        Dim intValue2 As Integer?   '與上句等效

        intValue1 = 3
        intValue1 = Nothing '可為空,正確
        intValue2 = Nothing
         

        Dim intValue1 As New Nullable(Of Integer) '可為空的Integer型別

        intValue1 = Nothing
        If intValue1.HasValue Then '判斷是滯有值
            MessageBox.Show("有值")
        Else
            MessageBox.Show("空值")
        End If
              在取得這樣的型別時,當用判斷來說明值的情況。

4、泛型型別

      泛型有兩形式:泛型型別、泛型 方法。下面說明泛型型別

         泛型型別是用來定義類、結構、介面的模板。在使用泛型型別宣告變數時,需要提供真正(具體)的型別,以確定實際型別。

     (1)泛型的基本用法

        Dim data As New Generic.Dictionary(Of Integer, String)

        data.Add(3, "OK")
        data.Add(4, "dz")
        data.Add(1, "John")

        'KeyValuePair(Of Integer, String) 鍵值對元素
        For Each o As KeyValuePair(Of Integer, String) In data
            TextBox1.AppendText(o.Key & "," & o.Value & vbCrLf)
        Next
        '==========================
        Dim data2 As New Generic.Dictionary(Of Guid, Date)

        data2.Add(New Guid, Now)
        For Each o As KeyValuePair(Of Guid, Date) In data2
            TextBox1.AppendText(o.Key.ToString & "," & o.Value) 'Guid須轉String
        Next

           Generic.Dictionary(Of K,T)泛型,與List(Of  T)型別類似,但需兩個型別引數來提供鍵與值(Key,Value)。

           新的Dictionary型別只接受特定型別的鍵與值,如上面第一個是Integer與String。第二個只接收Guid與Date。


             上面是宣告時的情況,下面是作返回值的情況

    Private Function reGeneric() As Generic.Dictionary(Of Integer, String) '返回值型別
        Dim data As New Generic.Dictionary(Of Integer, String)
        data.Add(3, "dx")
        data.Add(2, "qxj")
        data.Add(1, "ase")
        Return data  '返回泛型
    End Function

            可以這樣呼叫上面函式:
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim data As New Generic.Dictionary(Of Integer, String)

        data = reGeneric() '呼叫,取得泛型
        For Each o As KeyValuePair(Of Integer, String) In data
            TextBox1.AppendText(o.Key & "," & o.Value & vbCrLf)
        Next
    End Sub

          泛型還可以作為傳參:
    Private Sub useGeneric(ByVal k As Generic.Dictionary(Of Integer, String)) '泛型作引數
        'add code
    End Sub

         (2)繼承

             定義新類時,可以繼承泛型型別。

             例如:.net BCL定義的System.ComponentModel.BindingList(Of  T)泛型型別,它用於建立支援資料繫結的集合。

                          可以將其作用基類,建立支援資料繫結的強型別集合。

Public Class Form1
    Dim list As New CustomerList

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        DataGridView1.DataSource = list
    End Sub
End Class

Public Class Customer
    Public Property Name() As String
End Class

Public Class CustomerList
    Inherits System.ComponentModel.BindingList(Of Customer) '必須指明具體的型別(如Customer)

    Private Sub CustomerList_AddingNew(ByVal sender As Object, ByVal e As System.ComponentModel.AddingNewEventArgs) Handles Me.AddingNew
        Dim cust As New Customer
        cust.Name = "<new>"
        e.NewObject = cust
    End Sub
End Class

        上面繼承時,必須指明具體型別,於是可以用BindingList(Of  Customer)

        常規繼承概念也可以用在其中,比如:過載、重寫、事件等。

            

5、泛型方法

         泛型方法語法較複雜,較難理解。在呼叫泛型方法時,要使用定義該方法的型別引數外,還可以使用普通引數。

         泛型方法不必只在定義的泛型型別中使用,還可以任意的型別和模組中使用泛型方法。

          泛型方法的好處是:不需要使用Ctype或DirectCast轉換不同型別的引數與返回值。(因為泛型是CType與DirectCast替換的機制,它實際上仍然會轉換)

           下面過載泛型方法:

Public Class Form1
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim result As Boolean
        result = AreEqual(1, 2)
        result = AreEqual("one", "two")
        result = AreEqual(1, "two") '均正確寫法,轉為object比較

        '使用泛型
        result = AreEqual(Of Integer)(1, 2)
        result = AreEqual(Of String)("one", "two") '正確寫法
        result = AreEqual(Of Integer)(1, "two") '錯誤
    End Sub

    '不使用泛型
    Public Function AreEqual(ByVal a As Object, b As Object) As Boolean
        Return a.Equals(b)
    End Function

    '使用泛型(過載)
    Public Function AreEqual(Of T)(ByVal a As T, b As T) As Boolean
        Return a.Equals(b)
    End Function
End Class

           泛型方法會有兩套引數(分別用兩個括號):

                  第一套引數(第一個括號)用來定義方法中使用的型別

                  第二套引數(第二個括號)與我們平時的引數列表一樣,只不過用T等來代替要用的型別。

           上面如果在Option  Strict On時

6、建立泛型型別

        通過前面感性認識了:泛型型別與泛型 方法。

         泛型型別是定義類、結構、介面的模板,通過建立模板獲得更好效能實現程式碼重用。

       (1)泛型類

            建立泛型類模板與建立普通類類似,但前者要求提供使用的型別,這樣在使用時以便明確這個型別:

'建立泛型類(定義)
Public Class SingleLinkedList(Of T) 'T可自定,但在使用中,宣告時須指定明確的型別
    'add code
End Class
           上面在使用(宣告)中,就須指明T的具體型別,T與變數命名方式一樣。

           下面建立連結串列,為了適合不同型別情況,使用泛型。在這個巢狀類中先定義結點類Node:

Public Class SingleLinkedList(Of ValueType) '單向連結串列類。其中,自定型別ValueType
#Region "Node Class"
    Public Class Node '每個結點由當前值、指向下一個結點的引用,這兩個元素組成
        Private mValue As ValueType

        Public ReadOnly Property Value() As ValueType '當前結點的值
            Get
                Return mValue
            End Get
        End Property
        Public Property NextNode() As Node '下個結點的引用
        Public Sub New(ByVal value As ValueType, ByVal newNode As Node) '建立新結點
            mValue = value
            NextNode = newNode
        End Sub
    End Class
#End Region
End Class

      這樣在宣告使用中就可以使用具體型別,比如用Double型別的連結串列: 

        Dim list As New SingleLinkedList(Of Double)
      這時,在類的的ValueType實際上就變成了:
        Private mValue As Double

        實際上,在設計時(ValueType型別),被當作了Object(型別),故只能使用System.object型別上的方法:

                     Equals()、  GetHashValue()、     GetType()、   ReferenceEquals()、    Tostring()

        這將限制我們操作,並且智慧化提示也受限,後面將用約束概念,來明確選擇的型別,這樣擴充套件功能增強智慧化提示。

         然後,使用Node完善連結串列類:        

Public Class SingleLinkedList(Of ValueType) '單向連結串列類。其中,自定型別ValueType
#Region "Node Class"
    Public Class Node '每個結點由當前值、指向下一個結點的引用,這兩個元素組成
        Private mValue As ValueType

        Public ReadOnly Property Value() As ValueType '當前結點的值
            Get
                Return mValue
            End Get
        End Property
        Public Property NextNode() As Node '下個結點的引用
        Public Sub New(ByVal value As ValueType, ByVal newNode As Node) '建立新結點
            mValue = value
            NextNode = newNode
        End Sub
    End Class
#End Region

    Private mHead As Node '頭結點,也是當前結點。(按倒序加入結點,參看後圖)

    Default Public ReadOnly Property Item(ByVal index As Integer) As ValueType '獲取第N個結點值
        Get
            Dim current As Node = mHead
            For i As Integer = 1 To index
                current = current.NextNode
                If current Is Nothing Then
                    Throw New Exception("Item not found in list")
                End If
            Next
            Return current.Value
        End Get
    End Property

    Public Sub Add(ByVal value As ValueType) '新增結點到連結串列
        mHead = New Node(value, mHead)
    End Sub

    Public Sub Remove(ByVal value As ValueType) '從連結串列中移除結點
        Dim current As Node = mHead
        Dim preNode As Node = Nothing

        While current IsNot Nothing
            If current.Value.Equals(value) Then
                If preNode Is Nothing Then '是否為頭結點
                    mHead = current.NextNode
                Else
                    preNode.NextNode = current.NextNode '非頭結點(參看下圖)
                End If
                Exit Sub '已移除,退出
            End If
            preNode = current
            current = current.NextNode
        End While
        Throw New Exception("Item not found in list") '連結串列中未找到
    End Sub

    Public ReadOnly Property Count() As Integer '統計結點數
        Get
            Dim result As Integer = 0
            Dim current As Node = mHead
            While current IsNot Nothing
                result += 1
                current = current.NextNode
            End While
            Return result
        End Get
    End Property
End Class

           定義好連結串列類後,下面使用:

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim list As New SingleLinkedList(Of String)

        list.Add("one")
        list.Add("two")
        list.Add("three")
        list.Add("sichuan")
        list.Add("dazhou")
        TextBox1.Clear()
        TextBox1.AppendText(list.Count & vbCrLf)
        For i As Integer = 0 To list.Count - 1 '這裡只能從0,因為你不能預知是否有結點。
            TextBox1.AppendText(list.Item(i) & vbCrLf)
        Next
    End Sub
         執行,結果如下:

           

     (2)泛型類的其它功能

              Dictionay泛型有多個型別引數,還可帶其它普通型別:

Public Class MyType1(Of T, V) '帶兩個型別引數
    Private mValue As T
    Private mData As V

    Public Sub New(ByVal value As T, ByVal data As V) '帶兩個型別引數
        mValue = value
        mData = data
    End Sub
End Class

Public Class MyType2(Of T, V)
    Private mValue As T
    Private mData As V
    Private mMoney As Double

    Public Sub New(ByVal value As T, ByVal data As V, ByVal money As Double) '帶兩個型別引數及一個普通型別引數
        mValue = value
        mData = data
        mMoney = money
    End Sub
End Class


        (3)類、泛型類與繼承

          泛型類實際上是一種特殊的類。因此也具有繼承等特點。

          泛型類可以繼承現成的類,普通類也可繼承泛型類,泛型類可以繼承泛型類。還有複雜的泛型子類傳回泛型父類。

'泛型繼承現成的類
Public Class MyControls(Of T)
    Inherits Control
End Class

'基類泛型
Public Class GenericBase(Of T)
    'add code
End Class

'類繼承泛型
Public Class SubClass
    Inherits GenericBase(Of Integer) '必須指明型別
    'add code
End Class

'泛型繼承泛型(不同型別)
Public Class GenericSubClass1(Of T)
    Inherits GenericBase(Of Integer) '必須指明型別
    'add code
End Class

'泛型繼承泛型
Public Class GenericSubClass2(Of V) '在使用時(宣告)指明V型別
    Inherits GenericBase(Of V)  '與上V相同,故型別由子類傳遞迴父類
    'add code
End Class

'複雜子類泛型傳遞迴父類
Public Class GenericSubClass3(Of V)
    Inherits GenericBase(GenericBase(of V)) '子型別傳回父類
    'add code
End Class


           (4)結構、介面中使用泛型

            結構與類一樣,在結構中也可以使用泛型:

'結構中使用泛型 
Public Structure MyCool(Of T)
    Public value As T
End Structure

             這樣,在使用時就可以:   Dim  data  as MyCool(Of   Guid)

            還可以定義泛型類介面型別。

             泛型介面與泛型類、泛型結構有所不同:它的實現依賴其它型別:

'介面使用泛型
Public Interface Icool(Of T)
    Sub DoWork(ByVal data As T)
    Function GetAnswer() As T
End Interface

Public Class ARegularClass
    Implements Icool(Of String) '必須指明型別
    Implements Icool(Of Integer)

    '============String時情況==============
    Public Sub DoWork(data As String) Implements Icool(Of String).DoWork
        'add code
    End Sub

    Public Function GetAnswer() As String Implements Icool(Of String).GetAnswer
        'add code
    End Function

    '==========Integer時情況=================
    Public Sub DoWork1(data As Integer) Implements Icool(Of Integer).DoWork
        'add code
    End Sub

    Public Function GetAnswer1() As Integer Implements Icool(Of Integer).GetAnswer
        'add code
    End Function
End Class
        上面泛型介面定義中無法明確型別,它依賴於ARegularClass中介面型別的定義(有兩個:Integer、String)

7、建立泛型方法

      泛型方法也可以泛型類(類、結構、介面)中實現。

       也可以在普通(類、結構、介面、模組)中實現,只不過此時的型別引數是在方法指定(而不是在類、結構、介面上指定)

       下面泛型 方法在普通中,故型別直接在方法中指定(T):

'模組中使用泛型
Public Module Comparisons1
    Public Function AreEqual(Of T)(ByVal a As T, ByVal b As T) As Boolean
        Return a.Equals(b)
    End Function
End Module

'類中使用泛型
Public Class Comparisons2
    Public Function AreEqual(Of T, R)(ByVal a As T, ByVal b As R) As R '返回也可為R
        'add code
    End Function
End Class


四、約束(限制)

              泛型型別、泛型方法在編寫程式碼時,型別引數都被當作System.Object型別處理,這限制了使用型別引數的引數與變數的功能。

             即,只能進行賦值和呼叫所有System.Object變數的幾個方法,大大限制了泛型的用途。

             約束就是來突破這種限制,並提供控制機制。約束提供指定規則,宣告執行時可以代替型別引數型別。

             使用約束,可以限定型別引數必須是一個類或結構,也可限定型別引數必須實現介面或繼承某基類。這樣智慧提示就生效了。

           通俗地說:約束暗示了某具體型別,使得智慧提示生效。

1、型別約束

         這是常用約束,它限制某型別引數必須是指定類的子類或者必須實現指定的介面。

         改變上面的連結類成為ComparableLinkedList,這裡泛型指明瞭ValueType,同時也指明它是一個介面IComparable。

         因此,智慧提示會對ValueType型別提示IComparable的屬性和方法:

Public Class Form1
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim list As New ComparableLinkedList(Of String)

        list.Add("one")
        list.Add("two")
        list.Add("three")
        list.Add("sichuan")
        list.Add("dazhou")
        TextBox1.Clear()
        TextBox1.AppendText(list.Count & vbCrLf)
        For i As Integer = 0 To list.Count - 1 '這裡只能從0,因為你不能預知是否有結點。
            TextBox1.AppendText(list.Item(i) & vbCrLf)
        Next
    End Sub
End Class


Public Class ComparableLinkedList(Of ValueType As IComparable) '型別引數同時也是介面(用於比較)
#Region "Node Class"
    Public Class Node
        Private mValue As ValueType
        Public ReadOnly Property Value() As ValueType
            Get
                Return mValue
            End Get
        End Property
        Public Property NextNode() As Node
        Public Sub New(ByVal value As ValueType, ByVal newNode As Node)
            mValue = value
            NextNode = newNode
        End Sub
    End Class
#End Region

    Private mHead As Node
    Default Public ReadOnly Property Item(ByVal index As Integer) As ValueType
        Get
            Dim current As Node = mHead
            For i As Integer = 1 To index
                current = current.NextNode
                If current Is Nothing Then
                    Throw New Exception("Item not found in list")
                End If
            Next
            Return current.Value
        End Get
    End Property

    '=========這是原來不是介面時的新增元素的方法=========
    Public Sub Add1(ByVal value As ValueType)
        mHead = New Node(value, mHead)
    End Sub

    '======現在型別引數同時是介面(可用於比較)的方法=====
    Public Sub Add(ByVal value As ValueType)
        If mHead Is Nothing Then
            mHead = New Node(value, mHead) '連結串列無結點時,直接新增
        Else
            Dim current As Node = mHead
            Dim preNode As Node = Nothing
            While current IsNot Nothing
                If current.Value.CompareTo(value) > 0 Then '===關鍵,介面使用(比較)
                    If preNode Is Nothing Then
                        mHead = New Node(value, mHead) '連結串列頭
                    Else
                        preNode.NextNode = New Node(value, current) '連結串列中
                    End If
                    Exit Sub
                End If
                preNode = current
                current = preNode.NextNode
            End While
            preNode.NextNode = New Node(value, Nothing) '連結串列尾
        End If
    End Sub

    Public Sub Remove(ByVal value As ValueType)
        Dim current As Node = mHead
        Dim preNode As Node = Nothing

        While current IsNot Nothing
            If current.Value.Equals(value) Then  
                If preNode Is Nothing Then
                    mHead = current.NextNode
                Else
                    preNode.NextNode = current.NextNode
                End If
                Exit Sub
            End If
            preNode = current
            current = current.NextNode
        End While
        Throw New Exception("Item not found in list")
    End Sub
    Public ReadOnly Property Count() As Integer
        Get
            Dim result As Integer = 0
            Dim current As Node = mHead
            While current IsNot Nothing
                result += 1
                current = current.NextNode
            End While
            Return result
        End Get
    End Property
End Class
            

           由於上面同時是介面所以可以用CompareTo方法:

                             If current.Value.CompareTo(value) > 0 Then

           上面是限定為介面,下面還可限定為子類(Windows窗體控制Control)

           同時也是在方法中限定(上面是在泛型型別中):

'約束型別引數必須是某類的子類(下例為Control的泛型方法)
Public Shared Sub ChangControl(Of C As Control)(ByVal con As C)
    con.Anchor = AnchorStyles.top Or AnchorStyles.left
End Sub



'===========================================================
'約束型別引數為特定的泛型
Public Class ListClass(Of T, V As Generic.List(Of T))
    'add code
End Class
'使用時這樣:
Dim list As ListClass(Of Integer, Generic.List(Of Integer))
          上面還限制了部分引數須是某泛型型別(V)

2、類、結構的約束

       下面限制類型引數必須是值型別或引用型別:

'約束(限制)型別引數是引用型別
Public Class ReferenceOnly(Of T As Class)
    'add code
End Class


'約束(限制)型別引數是值型別
Public Class ValueOnly(Of T As Structure)
    'add code
End Class


3、New約束

          有時需建立型別引數定義的型別例項,必須使用New約束,來確保該型別有預設的公共建構函式。

Public Class Factories(Of T As New)
    Public Function CreateT() As T    '必須確保T有預設的建構函式,否則出錯
        Return New T
    End Function
End Class

          型別引數T必須有公共的預設建構函式。若給T沒指定建構函式的型別會出錯。有了T的預設建構函式後,就可以CreateT建立例項。


4、多個約束

        可以指定型別引數為幾個約束,用花括號。

       下面約束:必須是引用型別,且必須有公共的預設建構函式:

Public Class Factories(Of T As {New, Class}) '限制類型引數可以為多種情況(花括號)
    Public Function CreateT() As T
        Return New T
    End Function
End Class


5、泛型與後期繫結

         泛型的變數與引數在模板程式碼中被當作Object處理,雖然用約束解決了部分問題、擴充套件了變數型別,但仍受限制。

         比如,並不知道指定的型別是否支援+-等運算子過載:

Public Function Add(Of T)(ByVal a As T, ByVal b As T) As T
    Return a + b '錯誤,因不知道是否支援運算子過載
End Function

'改為下面(option strict off)
Public Function Add1(Of T)(ByVal a As T, ByVal b As T) As T
    Return CObj(a) + CObj(b) '此時應過載+,這樣才不會出錯
End Function
          

五、協變與逆變

        協變逆變利用繼承關係 對不同引數型別或返回值型別 的委託或者泛型介面之間做轉變。

        協變和逆變是說明父類與子類的相互轉換。

         簡單地說:子--》父(協變);父--->子(逆變)

                             因為子到父的轉換永遠正確(協變),而父到子的轉換不一定正確所以也稱(逆變)

         例如Animal是父類,Dog是從Animal繼承的子類。

          如果一個方法要接受Dog引數,那麼另一個接受Animal引數的方法肯定也可以接受這個方法的引數,這是Animal向Dog方向的轉變是逆變。

          如果一個方法要求的返回值是Animal,那麼返回Dog的方法肯定是可以滿足其返回值要求的,這是Dog向Animal方向的轉變是協變。

         它們的主要應用場合是多型。

1、協變

      子--->父

'父類
Public Class Parent(Of T)
    'add code
End Class

'子類
Public Class ChildClass(Of T)
    Inherits Parent(Of T)

    'add code
End Class

'多型應用中,協變
Public Class CoVariance
    Public Sub MainMethod()
        Dim cc As New ChildClass(Of String)
        Dim dad As Parent(Of String)

        dad = cc    '子類賦值給父類,協變
    End Sub
End Class


2、逆變

      父---->子

'父類
Public Class Base

End Class

'子類
Public Class Derived
    Inherits Base

End Class

'應用(泛型逆變)
Public Class ContraVariance
    '封裝一個方法,有一個引數且無返回值,即baseMethod(byval param as Base)
    Private baseMethod As Action(Of Base) = Sub(param As Base)
                                                'add code
                                            End Sub
    'derivedMethod(byval param as Derived)
    Private derivedMethod As Action(Of Derived) = baseMethod

    Public Sub MainMethod()
        Dim d As Derived = New Derived()

        derivedMethod(d)
        baseMethod(d)
    End Sub
End Class


相關推薦

VB.net學習筆記陣列集合

變數、陣列、集合、泛型的發展            最開始用記憶體中一個位置對映一個值,用變數來“使用”這個值。            進一步發展,用變數來引用一組值,這就是陣列。由陣列概念,發展出連結串列、堆、棧,進行排序、檢索等。            但這並不能完

Python學習筆記

插入 imp 集合類 屬性 counter 以及 雙向 ror 簡單的 一、collections介紹   collections是Python中內建的一個集合模塊,提供了許多有用的集合類 二、namedtuple   namedtuple是一個函數,用來創建一個類似類的自

python學習筆記面向對象編程,類

時代 alt 類名 rst tps 玉溪 connect nbsp nco 一、面向對象編程 面向對象,是一種程序設計思想。 編程範式:編程範式就是你按照什麽方式去編程,去實現一個功能。不同的編程範式本質上代表對各種類型的任務采取的不同的解決問題的思路,兩種最重要的編程範式

Linux學習筆記文件壓縮

文件壓縮一、常見的壓縮文件 Windows .rar .zip .7z Linux .zip,.gz,.bz2,.xz,.tar.gz,.tar.bz2,.tar.xz文件壓縮可以節省內存,也可以節省傳輸速度 二、gzip首先創建了一個文件夾 /tmp/d6z/找了些比較大的文件寫入1.txt例如find

c++ primer第五版----學習筆記

部分習題解答: 19.1、19.2: #include <iostream> #include <cstdlib> using namespace std; void *operator new(size_t size) { cout << "new(

c++ primer第五版----學習筆記

文章目錄 ==特殊工具與技術== 1.控制記憶體分配 1.1 過載new和delete 1.2 定位new表示式 2. 執行時型別識別 2.1 dynamic_cast運算子 2.2 typeid運算子

機器學習筆記:TensorFlow實戰多執行緒輸入資料

1 - 引言 為了加速模型訓練的時間,TensorFlow提供了一套多執行緒處理輸入資料的框架。 下面我們來詳細的介紹如何使用多執行緒來加速我們的模型訓練速度 2 - 佇列與多執行緒 在TensorFlow中,佇列和變數類似,我們可以修改它們的狀態。下面給出一個示例來展示如

javaweb學習筆記:連線池

目錄 1.連線池概念 資料庫連線池(Connection pooling)是程式啟動時建立足夠的資料庫連線,並將這些連線組成一個連線池,由程式動態地對池中的連線進行申請,使用,釋放。 資料庫連線池的基本思想就是為資料庫連線建立一個“緩衝池”。預先在緩衝池

ESP32 學習筆記High Resolution Timer

文章目錄 高解析度定時器 概述 使用 `esp_timer` API 獲得當前時間 應用示例 API 參考 高解析度定時器 概述 雖然 FreeRTOS 提供軟體定時器,但這些定時器有

微信小程式學習筆記video視訊

<view class="section tc"> <video id="myVideo" src="http://wxsnsdy.tc.qq.com/105/20210/

機器學習筆記——最大熵原理和模型定義

一、最大熵原理     最大熵原理是概率模型學習的一個準則。最大熵原理認為,在學習概率模型時,在所有可能的概率分佈中,熵最大的模型是最好的模型。通常用約束條件來確定概率模型的集合,所以,最大熵模型也可以表述為在滿足約束條件的模型集合中選取熵最大的模型。   

學習筆記

DATA: i1 TYPE i VALUE 2, i2 TYPE i VALUE 3. PERFORM add USING i1 i2. PERFORM add USING i1 i2. FORM add USING value(a) TYPE i valu

Java for Web學習筆記:Session3Session Listener

Session Listener 可以通過Listner來監聽session的變化,這就是所謂的publish and subscribe模型。這是一種訊息資訊釋出一方叫釋出者,資訊的接收方叫訂閱者,實際也是事件驅動的高大上說法,訂閱某個事件,然後觸發處理。這種方式最大的作用是將進行session變化以及s

unity3d學習筆記--ngui製作人物頭頂的頭像和血條

本系列文章由Aimar_Johnny編寫,歡迎轉載,轉載請標明出處,謝謝。 http://blog.csdn.net/lzhq1982/article/details/18793479 先上張圖,自己做的一個demo。 這裡的人物頭像和血條是在3d世界生成的,所以

模式識別Pattern Recognition學習筆記--多層感知器模型MLP

       早前已經學習了感知器學習演算法,主要通過對那些錯分類的樣本進行求和來表示對錯分樣本的懲罰,但明顯的它是一個線性的判別函式;而且上節學到了感知器神經元(閾值邏輯單元),對於單個的感知器神經元來說,儘管它能夠實現線性可分資料的分類問題(如與、或問題),但是卻無法解

OpenCV2學習筆記:Kalman濾波演算法

在視訊跟蹤處理中,預測目標運動軌跡是一項基本任務。目標運動狀態估計的目的有三個:一是對目標過去的狀態進行平滑;二是對目標現在的運動狀態進行濾波;三是對目標未來的運動狀態進行預測。物體的運動狀態一般包括目標位置、速度、加速度等。著名的Kalman濾波技術就是其中一

Linux學習筆記磁盤格式化磁盤掛載手動增加swap空間

swap 根目錄 cad inode 筆記 pre images 實例 exe 一、磁盤格式化 cat /etc/filesystems 查看系統支持的文件格式 mount 查看系統的文件格式可以看到根目錄和、boot都是xfs 格式centos6使用的是ext4cento

hadoop學習筆記:MapReduce數據類

筆記 ash all 記錄 write 一個 操作 png bool 一、序列化 1 hadoop自定義了數據類型,在hadoop中,所有的key/value類型必須實現Writable接口。有兩個方法,一個是write,一個是readFileds。分別用於讀(反序列化操

C#學習筆記:抽象方法抽象類多態和接口

具體實現 烏龜 ima 索引器 over protect ret 需要 技術 using System; using System.Collections.Generic; using System.Linq; using System.Text;

Unity3D學習筆記:IK動畫粒子系統和塔防

hpa 狀態 ram erl 代碼調整 tar 處理 rtu 需要 新動畫系統: 反向動力學動畫(IK功能): 魔獸世界(頭部動畫),神秘海域(手部動畫),人類一敗塗地(手部動畫) 如何啟用(調整) 1、必須是新動畫系統Animator 設置頭、手、肘的目標點 2、動畫