14、VB 2005 和 .NET 2.0 中的泛型:深入解析与应用

VB 2005 和 .NET 2.0 中的泛型:深入解析与应用

1. 泛型简介

泛型是 VB 2005 和 .NET 2.0 中一项重大的新增特性,它允许创建开放式类型,这些类型在运行时会转换为封闭式类型。只有封闭式类型才能被实例化,且每个唯一的封闭式类型本身就是一个独特的类型。

创建泛型类型时,会使用类型参数来定义开放式类型。新的 Of 关键字后面跟着类型参数,用于指定在运行时创建泛型类型或执行泛型成员时应使用的类型。泛型还消除了装箱和拆箱操作的需要,并且可以通过指定类型参数约束来提高类型安全性。

以下是一个声明泛型类型的示例:

Public Class MyCollection(Of T)
    Private Storage As T()
    Public Sub New()
    End Sub
End Class

在这个例子中, MyCollection(Of T) 是一个泛型类型,它将集合中的类型视为未指定类型。 T 只是任何类型的占位符。当使用 MyCollection(Of T) 时,可以通过指定 T 所代表的具体类型来创建封闭式类型,例如:

Public Sub SomeMethod()
    Dim collectionOfNumbers As MyCollection(Of Integer) = _
        New MyCollection(Of Integer)()
End Sub

这里的 MyCollection(Of Integer) 就是封闭式类型,它可以像其他已声明的类型一样使用,并且遵循与非泛型类型相同的规则。在实例化时, MyCollection(Of T) 实现背后的中间语言(IL)代码会进行即时(JIT)编译,将 T 的所有使用替换为 Integer 类型。

需要注意的是,从同一泛型类型创建的所有唯一构造类型实际上是完全不同的类型,它们没有隐式转换能力。例如, MyCollection(Of Long) MyCollection(Of Integer) 是完全不同的类型,不能进行如下操作:

'THIS WILL NOT WORK!
Public Sub SomeMethod(ByVal intNumbers As MyCollection(Of Integer))
    Dim longNumbers As MyCollection(Of Long) = intNumbers ' ERROR!
End Sub
2. 泛型的效率和类型安全性

泛型在 VB 中的一个显著优势是效率提升。基于 System.Array 的常规数组可以包含多种类型的实例,但这也带来了一些缺点。例如:

Public Sub SomeMethod(ByVal col As ArrayList)
    For Each o As Object In col
        Dim iface As ISomeInterface = CType(o, ISomeInterface)
        o.DoSomething()
    Next o
End Sub

由于公共语言运行时(CLR)中的所有内容都派生自 System.Object ,通过 col 参数传入的 ArrayList 可能包含各种不同的对象,其中一些可能并未实现 ISomeInterface ,这可能会导致 InvalidCastException 异常。而使用泛型可以利用编译器的类型引擎在编译时发现此类问题,例如:

Public Sub SomeMethod(ByVal col As IList(Of ISomeInterface))
    For Each iface As ISomeInterface In col
        iface.DoSomething()
    Next iface
End Sub

在这个例子中,方法接受 IList(Of T) 接口,由于构造类型的类型参数是 ISomeInterface ,列表只能包含该类型的对象,编译器可以强制执行强类型安全。

当类型参数是值类型时,泛型的效率提升更为明显。将值类型插入 System.Collections 命名空间中的集合(如 ArrayList )时,必须先进行装箱操作,因为 ArrayList 维护的是 System.Object 类型的集合。这会导致严重的效率问题,因为每次插入、引用或提取整数时都需要进行装箱和拆箱操作。而泛型可以避免这种装箱/拆箱循环,例如:

Imports System
Imports System.Collections
Imports System.Collections.Generic

Public Class EntryPoint
    Shared Sub Main()
    End Sub

    Public Sub NonGeneric(ByVal stack As Stack)
        For Each o As Object In stack
            Dim number As Integer = CInt(Fix(o))
            Console.WriteLine(number)
        Next o
    End Sub

    Public Sub Generic(ByVal stack As Stack(Of Integer))
        For Each number As Integer In stack
            Console.WriteLine(number)
        Next number
    End Sub
End Class

通过比较 NonGeneric Generic 方法生成的 IL 代码,可以发现 NonGeneric 方法有更多的指令,主要是因为它需要进行类型强制转换和拆箱操作。此外, NonGeneric 方法在运行时遇到无法显式转换为整数的对象时可能会抛出 InvalidCastException 异常。

3. 泛型类型占位符命名约定

虽然泛型参数占位符的命名没有严格的规则,但建议至少提供一个能描述该类型用途的名称。通常,占位符标识符的首字母使用大写 T 来表示它是一个类型。类似于接口名称以大写 I 开头的命名约定,这样的命名方式可以使代码更易于阅读。如果泛型类型定义的类型参数易于理解,常见的做法是使用 T 表示类型, K 表示键, V 表示值。

4. 泛型类型定义和构造类型

泛型类型是一种已编译的类型,在从中创建封闭式类型之前无法使用。非泛型类型也称为封闭式类型,而泛型类型称为开放式类型。不过,可以通过泛型定义新的开放式类型,例如:

Public Class A(Of T)
    Private innerObject As T
End Class

Public Class Consumer(Of T)
    Private obj As A(Of Stack(Of T))
End Class

在这个例子中, Consumer(Of T) 是一个泛型类型,它包含一个基于另一个泛型类型的字段。在声明 Consumer(Of T).obj 字段的类型时, A(Stack(Of T)) 仍然是开放式的,直到有人基于 Consumer(Of T) 声明一个构造类型,从而为包含的字段创建封闭式类型。

5. 泛型类和结构

到目前为止,所有示例都展示了泛型类,但泛型规则同样适用于结构。只要类声明中包含类型参数列表,从那一刻起它就是一个泛型类型。同样,在泛型类型的作用域内声明的任何嵌套类声明,无论它是否为泛型,本身都是泛型类型,因为外部类型的完全限定名称需要一个类型参数才能完全指定嵌套类型。

泛型类型根据其类型参数列表中的参数数量进行重载。例如:

Public Class Container
End Class

Public Class Container(Of T)
End Class

Public Class Container(Of T, R)
End Class

这些声明在同一命名空间内都是有效的,只要每个泛型类型的类型参数数量不同,就可以根据 Container 标识符声明任意数量的泛型类型。

当声明泛型类型时,实际上是在声明一个开放式类型,因为其完全指定的类型尚不清楚。当基于泛型类型定义声明另一个类型时,就是在声明一个构造类型,例如:

Public Class A(Of T)
    Private field1 As Container(Of Integer)
    Private field2 As Container(Of T)
End Class

A(Of T) 的声明中,两个字段都是构造类型,但并非每个构造类型都是封闭式类型。只有 field1 是封闭式类型,而 field2 是开放式类型,因为其最终类型仍需在运行时根据 A(Of T) 的类型参数确定。

在 VB 中,所有名称声明都在特定的作用域内有效。例如,在方法内部声明的局部变量标识符仅在该方法的作用域内可用。泛型中的类型参数标识符也遵循类似的规则。在上述示例中,标识符 T 仅在类声明本身的作用域内有效。

泛型结构和类与普通结构和类一样,可以包含静态类型。但基于泛型类型的每个封闭式类型都包含其自己的静态类型实例。因此,如果需要在基于同一泛型类型的不同封闭式类型之间共享静态数据,必须采用其他方法,例如使用单例模式。

6. 泛型接口

除了类和结构,还可以创建泛型接口声明。这是结构和类泛型的自然延伸。.NET 标准库中声明的许多接口都非常适合创建其泛型版本,例如 IEnumerable(Of T) 。当泛型容器包含值类型时,它们比非泛型容器创建的代码更高效,因为避免了不必要的装箱操作。因此,任何泛型可枚举接口都必须有一种方法来枚举其中的泛型项,所以 IEnumerable(Of T) 存在,并且自己实现的可枚举容器应该实现这个接口,或者可以通过从 Collection(Of T) 派生自定义容器来免费获得该接口。

在创建自定义集合类型时,建议从 Collection(Of T) 派生,因为其他类型(如 List(Of T) )不是为派生而设计的,而是作为低级存储机制。 Collection(Of T) 实现了 Protected Friend 方法,可以重写这些方法来自定义其行为,而 List(Of T) 则没有。

7. 泛型方法

在结构、类或接口中存在的任何方法声明都可以声明为泛型方法。声明泛型方法时,只需在方法名后面但在方法参数列表之前附加一个类型参数列表。可以使用泛型参数声明方法参数列表中的任何类型,包括方法返回类型。与嵌套类一样,在泛型方法的作用域内重用外部类型标识符是不好的做法。

以下是一个泛型方法的示例,用于将另一个泛型容器的内容添加到当前容器中:

Imports System

Public Class MyContainer(Of T)
    Inherits List(Of T)
    Private impl As List(Of T) = New List(Of T)()

    Public Overloads Sub Add(ByVal item As T)
        MyBase.Add(item)
    End Sub

    ' Converter<TInput, TOutput> is a new delegate type introduced
    ' in the .NET Framework that can be wired up to a method that
    ' knows how to convert the TInput type into a TOutput type.
    Public Overloads Sub Add(Of R)(ByVal otherContainer As MyContainer(Of R), _
                                  ByVal converter As Converter(Of R, T))
        For Each item As R In otherContainer
            MyBase.Add(converter(item))
        Next item
    End Sub
End Class

Public Class EntryPoint
    Shared Sub Main()
        Dim lContainer As MyContainer(Of Long) = New MyContainer(Of Long)()
        Dim iContainer As MyContainer(Of Integer) = New MyContainer(Of Integer)()

        'Add 2 items to long container
        lContainer.Add(1)
        lContainer.Add(2)

        'Add 2 items to integer container
        iContainer.Add(3)
        iContainer.Add(4)

        'Convert and append integer container to long container
        lContainer.Add(iContainer, AddressOf EntryPoint.IntToLongConverter)

        For Each i As Integer In iContainer
            Console.WriteLine("iContainer Item: {0}", i)
        Next

        Console.WriteLine()

        For Each l As Long In lContainer
            Console.WriteLine("lContainer Item: {0}", l)
        Next l
    End Sub

    Private Shared Function IntToLongConverter(ByVal i As Integer) As Long
        Return i
    End Function
End Class

在这个示例中, MyContainer(Of T) 类有两个重载的 Add 方法。第一个方法用于添加类型为 T 的实例,第二个泛型方法 Add(Of R) 允许将另一个 MyContainer(Of R) 容器中的对象添加到当前容器中,前提是源容器中包含的类型可以转换为目标容器中包含的类型。为了实现这种转换,需要提供一个转换委托,如 System.Converter(Of T, Of R) 。在调用 Add(Of R) 方法时,必须提供一个指向知道如何从源类型转换为目标类型的方法的泛型 Converter(Of T, Of R) 委托实例。

8. 泛型委托

泛型经常用于容器类型的上下文中,其中封闭式类型的字段或内部数组基于给定的类型参数。泛型方法通过提供更细粒度的泛型作用域扩展了泛型类型的功能。

已经熟悉了普通委托,如果要声明一个接受两个参数(第一个是 Long 类型,第二个是 Object 类型)的委托,可以这样声明:

Public Delegate Sub MyDelegate(ByVal l As Long, ByVal o As Object)

在前面的部分中,已经看到了泛型转换器委托的使用,其声明如下:

Public Delegate Function Converter(Of TInput, TOutput)(ByVal input As TInput) _
    As TOutput

它看起来与其他委托类似,只是在委托名称后面紧跟一个类型参数列表,具有泛型的特征。与非泛型委托类似于没有主体的方法声明一样,泛型委托声明几乎与没有主体的泛型方法声明相同。类型参数列表跟在委托名称后面,但在委托的参数列表之前。

泛型转换器在其类型参数列表中使用占位符标识符 TInput TOutput ,这些类型在委托声明的其他地方也会使用。在泛型委托声明中,类型参数列表中的类型在整个委托声明的作用域内有效,包括返回类型。

创建 Converter(TInput, TOutput) 委托的实例与创建其他委托的实例相同。创建泛型委托实例时,可以使用 New 运算符并在编译时显式提供类型列表,或者使用前面 MyContainer(Of T) 示例中使用的缩写语法,在这种情况下,编译器会推断类型参数。以下是该示例的 Main 方法:

Shared Sub Main()
    Dim lContainer As MyContainer(Of Long) = New MyContainer(Of Long)()
    Dim iContainer As MyContainer(Of Integer) = New MyContainer(Of Integer)()

    'Add 2 items to long container
    lContainer.Add(1)
    lContainer.Add(2)

    'Add 2 items to integer container
    iContainer.Add(3)
    iContainer.Add(4)

    'Convert and append integer container to long container
    lContainer.Add(iContainer, AddressOf EntryPoint.IntToLongConverter)

    For Each i As Integer In iContainer
        Console.WriteLine("iContainer Item: {0}", i)
    Next

    Console.WriteLine()

    For Each l As Long In lContainer
        Console.WriteLine("lContainer Item: {0}", l)
    Next l
End Sub

在这个示例中,最后一个 Add 方法的第二个参数只是对方法的引用,而不是显式创建委托本身。这是由于语言定义的方法组转换规则起作用。当从方法创建实际的委托时,会使用复杂的模式匹配算法从 IntToLongConverter 方法本身的参数类型推断泛型的封闭式类型。实际上,在调用 Add(Of T) 方法时,在调用点没有任何显式的类型参数列表,编译器能够进行相同类型的模式匹配来推断调用的 Add(Of T) 方法的封闭式形式,在这种情况下是 Add(Of Integer) 。也可以显式提供所有类型,如下所示:

lContainer.Add(Of Integer)(iContainer, New Converter(Of Integer, Long) _
    (AddressOf EntryPoint.IntToLongConverter))

无论哪种方式,生成的 IL 代码都是相同的。大多数情况下,可以依赖编译器的类型推断引擎,但根据代码的复杂性,可能需要显式提供类型列表。

泛型委托不仅可以提供一种将类型转换从容器类型外部化的方法,还可以帮助解决特定的问题。例如,以下代码存在问题:

' THIS WON'T WORK AS EXPECTED!!!
Imports System
Imports System.Collections.Generic

Public Delegate Sub MyDelegate(ByVal i As Integer)

Public Class DelegateContainer(Of T)
    Private imp As List(Of T) = New List(Of T)()

    Public Sub Add(ByVal del As T)
        imp.Add(del)
    End Sub

    Public Sub CallDelegates(ByVal k As Integer)
        For Each del As T In imp
            'del(k)
        Next del
    End Sub
End Class

Public Class EntryPoint
    Shared Sub Main()
        Dim delegates As DelegateContainer(Of MyDelegate) = _
            New DelegateContainer(Of MyDelegate)()
        delegates.Add(AddressOf EntryPoint.PrintInt)
    End Sub

    Private Shared Sub PrintInt(ByVal i As Integer)
        Console.WriteLine(i)
    End Sub
End Class

这段代码可以编译,但如果取消 CallDelegates 方法中注释行的注释,会得到错误: Expression is not a method 。问题在于编译器无法知道占位符 T 所代表的类型是一个委托,在运行时, del 代表的委托可能接受任意数量的参数。因此,很少有意义从泛型中创建一个封闭式类型,其中一个类型参数是委托类型,因为通常无法正常调用它。

可以使用泛型委托来解决这个问题,例如:

Imports System
Imports System.Collections.Generic

Public Delegate Sub MyDelegate(Of T)(ByVal i As T)

Public Class DelegateContainer(Of T)
    Private imp As List(Of MyDelegate(Of T)) = New List(Of MyDelegate(Of T))()

    Public Sub Add(ByVal del As MyDelegate(Of T))
        imp.Add(del)
    End Sub

    Public Sub CallDelegates(ByVal k As T)
        For Each del As MyDelegate(Of T) In imp
            del(k)
        Next del
    End Sub
End Class

Public Class EntryPoint
    Shared Sub Main()
        Dim delegates As DelegateContainer(Of Integer) = _
            New DelegateContainer(Of Integer)()
        delegates.Add(AddressOf EntryPoint.PrintInt)
        delegates.CallDelegates(42)
    End Sub

    Private Shared Sub PrintInt(ByVal i As Integer)
        Console.WriteLine(i)
    End Sub
End Class

这个示例会输出 42 。通过使用泛型委托,给编译器提供了更多关于要对这个委托执行的操作的信息,使得编译器能够生成有意义的泛型代码。

9. 泛型类型转换

从同一泛型类型形成的不同构造类型之间没有隐式类型转换。确定类型 X 的对象是否可以隐式转换为类型 Y 的对象的规则,同样适用于确定 List(Of Integer) 类型的对象是否可以转换为 List(Of Object) 类型的对象。如果需要进行这种转换,必须创建自定义的隐式转换运算符,就像在将没有继承关系的类型 X 的对象转换为类型 Y 的对象时一样。否则,需要创建一个转换方法来从一种类型转换到另一种类型。例如,以下代码是无效的:

' INVALID CODE!!!
Public Sub SomeMethod(ByVal theList As List(Of Integer))
    Dim theSameList As List(Of Object) = theList                    ' Ooops!!!
End Sub

如果查看 List(Of T) 的文档,会注意到一个名为 ConvertAll(Of TOutput)() 的泛型方法。使用这个方法可以将 List(Of Integer) 类型的泛型列表转换为 List(Of Object) 。但是,必须向该方法传递一个泛型转换委托的实例,这是该方法知道如何将每个包含的实例从源类型转换为目标类型的唯一方法。即使可以调用一个方法将 List(Of Integer) 转换为 List(Of Object) ,仍然必须提供将 Integer 转换为 Object 的显式方式。

熟悉策略模式的人可能会发现这是一个熟悉的概念。实际上,可以在运行时为 ConvertAll(Of TOutput) 方法提供一种对包含的实例进行转换的方式,根据转换的复杂性,可以针对程序运行的平台进行调整。例如,如果要将 List(Of Apples) 转换为 List(Of Oranges) ,可以提供几种不同的转换方法供选择,一种可能针对资源丰富的环境进行了高度优化,运行速度更快;另一种可能针对最小资源使用进行了优化,但速度较慢。在运行时,会构建适当的转换委托来绑定到适合当前任务的转换方法。

10. 可空类型

.NET 标准库提供了泛型类 System.Nullable(Of T) 。可空类型可以包含一个额外的值 Nothing ,表示空或未定义的值。以下是一个使用 System.Nullable(Of T) 的示例:

Imports System

Public Class Employee
    Public firstName As String
    Public lastName As String
    Public terminationDate As Nullable(Of DateTime)
    Public ssn As Nullable(Of Long)

    Public Sub New(ByVal firstName As String, ByVal lastName As String)
        Me.firstName = firstName
        Me.lastName = lastName
        Me.terminationDate = Nothing
        Me.ssn = Nothing
    End Sub
End Class

Public Class EntryPoint
    Shared Sub Main()
        Dim emp As Employee = New Employee("Jodi", "Fouche")
        Dim tempSSN As Long

        emp.ssn = 123456789

        Console.WriteLine("{0} {1}", emp.firstName, emp.lastName)

        If emp.terminationDate.HasValue Then
            Console.WriteLine("Start Date: {0}", emp.terminationDate)
        End If

        If emp.ssn.HasValue Then
            tempSSN = emp.ssn
        Else
            tempSSN = -1
        End If

        Console.WriteLine("SSN: {0}", tempSSN)
    End Sub
End Class

运行这段代码会产生以下结果:

Jodi Fouche
SSN: 123456789

这段代码演示了声明可空类型的方法。 Employee 类型中的可空字段使用 System.Nullable(Of DateTime) System.Nullable(Of Long) 类型声明。 Nullable(Of T) 的一个属性是 HasValue ,当可空值不为 Nothing 时返回 True ,否则返回 False

使用可空类型时,还需要考虑如何对可空类型进行赋值。在 Employee 类的构造函数中,可以看到首先将 Nothing 赋值给可空类型,编译器会对 Nothing 值进行隐式转换。最后,需要考虑将可空类型赋值给非可空类型的含义。例如,在 Main 方法中,要根据 emp.ssn 的值为 tempSSN 赋值,但由于 emp.ssn 是可空的,如果 emp.ssn 没有值, tempSSN 应该赋什么值呢?可以使用 HasValue 属性检查 emp.ssn ,如果它为 Nothing ,则将 tempSSN 赋值为 -1

可空类型非常适合表示系统中可能在语义上为空的值,在使用值来表示数据库字段中可为空的字段时非常方便。

11. 构造类型控制可访问性

从泛型类型构建构造类型时,必须考虑泛型类型和作为类型参数提供的类型的可访问性,以确定整个构造类型的可访问性。例如,以下代码是无效的,无法编译:

Public Class Outer
    Private Class Nested
    End Class

    Public Class GenericNested(Of T)
    End Class

    Private field1 As GenericNested(Of Nested)
    Public field2 As GenericNested(Of Nested) ' Ooops!
End Class

问题在于 field2 Nested 类型是私有的,所以 GenericNested(Of Nested) 不能是公共的。对于构造类型,可访问性是泛型类型和参数列表中提供的类型的可访问性的交集。

12. 约束

到目前为止,大多数泛型示例都涉及某种集合样式的类,用于持有特定类型的一组对象或值。但很多时候,需要创建不仅包含各种类型实例,还能直接使用这些对象的泛型类型。例如,假设有一个泛型类型,用于持有所有实现名为 Area 属性的任意几何形状的实例,并且需要该泛型类型实现一个 TotalArea 属性,用于累加包含的形状的所有面积。可以尝试编写如下代码:

Imports System
Imports System.Collections.Generic

Public Interface IShape
    ReadOnly Property Area() As Double
End Interface

Public Class Circle
    Implements IShape
    Private radius As Double

    Public Sub New(ByVal radius As Double)
        Me.radius = radius
    End Sub

    Public ReadOnly Property Area() As Double Implements IShape.Area
        Get
            Return 3.1415 * radius * radius
        End Get
    End Property
End Class

Public Class Rect
    Implements IShape
    Private width As Double
    Private height As Double

    Public Sub New(ByVal width As Double, ByVal height As Double)
        Me.width = width
        Me.height = height
    End Sub

    Public ReadOnly Property Area() As Double Implements IShape.Area
        Get
            Return width * height
        End Get
    End Property
End Class

Public Class Shapes(Of T)
    Private shapes As List(Of T) = New List(Of T)()

    Public ReadOnly Property TotalArea() As Double
        Get
            Dim acc As Double = 0
            For Each shape As T In shapes
                ' THIS WON'T COMPILE!!!
                acc += shape.Area
            Next shape
            Return acc
        End Get
    End Property

    Public Sub Add(ByVal shape As T)
        shapes.Add(shape)
    End Sub
End Class

Public Class EntryPoint
    Shared Sub Main()
        Dim shapes As Shapes(Of IShape) = New Shapes(Of IShape)()
        shapes.Add(New Circle(2))
        shapes.Add(New Rect(3, 5))
        Console.WriteLine("Total Area: {0}", shapes.TotalArea)
    End Sub
End Class

这段代码无法编译,错误信息为 'Area' is not a member of 'T' 。要求包含的类型 T 支持 Area 属性类似于一个契约,在 VB 中,当提到契约时,通常会想到接口。因此,让 Circle Rect 类都实现了 IShape 接口,但这仍然不足以让编译器编译上述代码。

由于构造类型是在运行时动态形成的,泛型必须有一种方法来强制执行类型 T 在运行时支持特定契约的规则。另一种尝试解决问题的方法可能如下:

Public Class Shapes(Of T)
    Private shapes As List(Of T) = New List(Of T)()

    Public ReadOnly Property TotalArea() As Double
        Get
            Dim acc As Double = 0
            For Each shape As T In shapes
                'DON'T DO THIS!
                Dim theShape As IShape = CType(shape, IShape)
                acc += theShape.Area
            Next shape
            Return acc
        End Get
    End Property

    Public Sub Add(ByVal shape As T)
        shapes.Add(shape)
    End Sub
End Class

这个修改后的 Shapes(Of T) 类确实可以编译并大部分时间正常工作,但由于 For Each 循环中的类型转换,这个泛型失去了一些价值。想象一下,如果尝试创建 Shapes(Of Integer) 构造类型,编译器会欣然同意,但当尝试从 Shapes(Of Integer) 实例获取 TotalArea 属性时,会在 TotalArea 属性访问器尝试将 Integer 转换为 IShape 时收到运行时异常。使用泛型的主要好处之一是提高类型安全性,但在这个例子中,却完全抛弃了类型安全性。

解决方法在于一个称为泛型约束的概念。以下是实现泛型约束的代码:

Public Class Shapes(Of T As IShape)
    Private shapes As List(Of T) = New List(Of T)()

    Public ReadOnly Property TotalArea() As Double
        Get
            Dim acc As Double = 0
            For Each shape As T In shapes
                acc += shape.Area
            Next shape
            Return acc
        End Get
    End Property

    Public Sub Add(ByVal shape As T)
        shapes.Add(shape)
    End Sub
End Class

注意类声明第一行中使用 As 关键字的额外子句,它表示“定义类 Shapes(Of T) ,其中 T 必须实现 IShape ”。现在,编译器有了强制执行类型安全所需的一切,JIT 编译器也有了在运行时构建有效代码所需的一切。编译器得到了一个提示,当尝试创建 T 不实现 IShape 的构造类型时,会以编译时错误的形式通知。

约束的语法很简单。多个 As 子句可以为每个类型参数有一个 As 子句。在 As 子句中,类型参数后面可以列出任意数量的接口(用花括号括起来),但最多只能有一个类。这种限制是合理的,因为给定的类型只能从一个类派生,但可以实现无限数量的接口。此外,可以在特定类型参数的约束子句中使用特殊关键字。由于 CLR 没有多重继承的概念,只有一个约束可以指定类类型,这个约束称为主约束。主约束可以列出特殊单词 class structure ,用于指示类型参数必须是类或结构。约束子句还可以包含所需数量的次要约束,通常是一个接口列表。最后,可以列出一个形式为 New 的构造函数约束,它要求参数化类型具有默认的无参数构造函数。类类型必须有显式定义的默认构造函数,而值类型由于有系统生成的默认构造函数, New 约束是自动的。

以下是一个显示两个约束子句的示例:

Imports System
Imports System.Collections.Generic

Public Interface IValue
    'IValue methods.
End Interface

Public Class MyDictionary(Of TKey As _
    {Structure, IComparable(Of TKey)}, TValue As {IValue, New})
    Private imp As Dictionary(Of TKey, TValue) = New Dictionary(Of TKey, TValue)()

    Public Sub Add(ByVal key As TKey, ByVal val As TValue)
        imp.Add(key, val)
    End Sub
End Class

在这个例子中, MyDictionary(Of TKey, Of TValue) 的声明方式使得键值被约束为值类型,并且要求 TKey 类型实现 IComparable(Of TKey) 接口。这个示例展示了两个约束子句,每个类型参数一个。在这种情况下,允许 TValue 类型是结构或类,但要求它支持定义的 IValue 接口以及默认构造函数。

总体而言,VB 泛型中内置的约束机制很简单。随着语言和 CLR 的发展,随着对泛型的更多应用的探索,这个领域可能会有一些新增内容。最后,泛型接口的约束格式与泛型类和结构的约束格式相同。

13. 非类类型的约束

到目前为止,已经在类、结构和接口的上下文中讨论了约束。实际上,任何可以泛型声明的实体都可以有一个可选的约束子句。对于泛型方法和委托声明,约束子句跟在方法或委托的形式参数列表之后。使用约束子句与方法和委托声明会产生一些看起来奇怪的语法,例如:

Imports System

Public Delegate Function Operation _
    (Of T1 As Structure, T2 As Structure, R As Structure) _
    (ByVal val1 As T1, ByVal val2 As T2) As R

Public Class EntryPoint
    Public Shared Function Add(ByVal val1 As Integer, ByVal val2 As Single) _
        As Double
        Return val1 + val2
    End Function

    Shared Sub Main()
        Dim op As Operation(Of Integer, Single, Double) = _
            New Operation(Of Integer, Single, Double)(AddressOf EntryPoint.Add)
        Console.WriteLine("{0} + {1} = {2}", 1, 3.2, op(1, 3.2F))
    End Sub
End Class

这个示例会输出:

1 + 3.2 = 4.2000000

在这个例子中,声明了一个泛型委托用于一个接受两个参数并返回一个值的运算符方法,约束是参数和返回值都必须是值类型。对于泛型方法,约束子句跟在方法声明之后但在方法体之前。注意在 Main 方法中创建实例时,必须告诉编译器需要的 Operation(Of T1, T2, R) 委托的确切构造类型。

14. 泛型系统集合

在 VB 和 CLR 中,泛型最自然的用途似乎是用于集合类型。这可能是因为与 System.Collections 命名空间中的集合类型相比,使用泛型容器来保存值类型时可以获得巨大的效率提升。当然,也不能忽视使用泛型集合带来的额外类型安全性。每次获得额外的类型安全性时,都可以保证减少运行时类型转换异常,因为编译器可以在编译时捕获其中的许多异常。

建议查看 .NET 框架文档中 System.Collections.Generic 命名空间的内容,在那里可以找到框架提供的所有泛型集合类,包括 Dictionary(Of TKey, TValue) LinkedList(Of T) List(Of T) Queue(Of T) SortedDictionary(Of TKey, TValue) SortedList(Of T) Stack(Of T)

根据它们的名称,这些类型的用途与 System.Collections 下的非泛型类相比应该很熟悉。虽然 System.Collections.Generic 命名空间中的容器集合可能看起来并不完全满足需求,但它为创建自己的集合提供了可能性,特别是考虑到 System.Collections.ObjectModel 中的可扩展类型。

在创建自己的集合类型时,经常需要能够比较包含的对象。虽然使用内置的相等和不等运算符进行比较可能感觉很自然,但应该避免这样做,因为类和结构对运算符的支持虽然是可能的,但不是公共语言规范(CLS)的一部分,有些语言不支持运算符。因此,容器必须为包含不支持比较运算符的类型的情况做好准备,这也是 IComparer IComparable 等接口存在的原因之一。

当在 System.Collections 中创建 SortedList 类型的实例时,有机会提供一个支持 IComparer 接口的对象实例, SortedList 在需要比较其包含的两个键实例时会使用该对象。如果不提供支持 IComparer 的对象, SortedList 会在包含的键对象上查找 IComparable 接口进行比较。自然地,如果包含的键对象不支持 IComparable ,就需要提供一个显式的比较器,接受 IComparer 类型的构造函数重载就是为此而存在的。

泛型版本的排序列表 SortedList(Of TKey, TValue) 遵循类似的模式。创建 SortedList(Of TKey, TValue) 时,可以选择提供一个实现 IComparer(Of T) 接口的对象来比较两个键。如果不提供, SortedList(Of TKey, TValue) 默认使用所谓的泛型比较器。泛型比较器是一个从 Comparer(Of T) 类派生的对象,可以通过 Comparer(Of T).Default 属性获得。基于非泛型 SortedList ,可能会认为如果 SortedList(Of TKey, TValue) 的创建者不提供比较器,它只会在包含的键类型上查找 IComparable(Of T) 接口,但这种方法会导致问题,因为包含的键类型可能支持 IComparable(Of T) 或非泛型 IComparable 。因此,默认比较器充当了一个额外的间接层,它会检查类型参数中提供的类型是否实现 IComparable(Of T) ,如果没有,会检查它是否支持 IComparable ,并使用它找到的第一个接口。这种额外的间接层为包含的类型提供了更大的灵活性。以下是一个示例:

Imports System
Imports System.Collections.Generic

Public Class EntryPoint
    Shared Sub Main()
        Dim list1 As SortedList(Of Integer, String) = _
            New SortedList(Of Integer, String)()
        Dim list2 As SortedList(Of Integer, String) = _
            New SortedList(Of Integer, String)(Comparer(Of Integer).Default)

        list1.Add(1, "one")
        list1.Add(2, "two")

        list2.Add(3, "three")
        list2.Add(4, "four")
    End Sub
End Class

在这个示例中,声明了两个 SortedList(Of TKey, TValue) 实例。第一个实例使用默认构造函数,第二个实例显式提供了一个整数比较器。在两种情况下,结果是相同的,因为在 list2 的构造函数中提供了默认泛型比较器。这里可以看到传递默认泛型比较器的语法,也可以为 Comparer 的类型参数列表提供任何其他支持 IComparable IComparable(Of T) 的类型。

15. 特定问题与解决方案
15.1 泛型类型中的转换和运算符

在泛型中,从一种类型转换为另一种类型或对参数化类型应用运算符可能会很棘手。为了说明这一点,开发一个表示复数的泛型 Complex 结构。假设希望能够指定内部用于表示复数实部和虚部的数值类型。以下是定义 Complex 数的代码:

Imports System

Public Structure Complex(Of T As Structure)
    Private mReal As T
    Private mImaginary As T

    Public Sub New(ByVal real As T, ByVal imaginary As T)
        Me.mReal = real
        Me.mImaginary = imaginary
    End Sub

    Public Property Real() As T
        Get
            Return mReal
        End Get
        Set(ByVal value As T)
            mReal = value
        End Set
    End Property

    Public Property Img() As T
        Get
            Return mImaginary
        End Get
        Set(ByVal value As T)
            mImaginary = value
        End Set
    End Property
End Structure

Public Class EntryPoint
    Shared Sub Main()
        Dim c As Complex(Of Int64) = New Complex(Of Int64)(4, 5)
    End Sub
End Class

为了让这个值类型更有用,可以添加一个 Magnitude 属性,用于返回两个分量相乘的平方根。尝试创建这样一个属性:

Imports System

Public Structure Complex(Of T As Structure)
    Private mReal As T
    Private mImaginary As T

    Public Sub New(ByVal real As T, ByVal imaginary As T)
        Me.mReal = real
        Me.mImaginary = imaginary
    End Sub

    Public Property Real() As T
        Get
            Return mReal
        End Get
        Set(ByVal value As T)
            mReal = value
        End Set
    End Property

    Public Property Img() As T
        Get
            Return mImaginary
        End Get
        Set(ByVal value As T)
            mImaginary = value
        End Set
    End Property

    Public ReadOnly Property Magnitude() As T
        Get
            'This will not compile.
            Return Math.Sqrt(mReal * mReal + mImaginary * mImaginary)
        End Get
    End Property
End Structure

Public Class EntryPoint
    Shared Sub Main()
        Dim c As Complex(Of Int64) = New Complex(Of Int64)(4, 5)
        Console.WriteLine("Magnitude is {0}", c.Magnitude)
    End Sub
End Class

如果尝试编译这段代码,会得到编译器错误 Operator '*' is not defined for types 'T' and 'T' 。这是在泛型代码中使用运算符的典型问题,编译问题源于必须以通用的方式编译泛型代码,以适应运行时形成的构造类型可能由不支持该运算符的值类型组成的情况。在这种情况下,编译器无法知道未来某个构造类型中为 T 提供的类型是否支持乘法运算符。

常见的解决方法是将操作从 Complex(Of T) 定义本身外部化,然后要求该定义的使用者提供操作。委托是实现这一目的的理想工具,以下是改进后的代码:

Imports System

Public Structure Complex(Of T As {Structure, IConvertible})
    ' Delegate for doing multiplication.
    Public Delegate Function BinaryOp(ByVal val1 As T, ByVal val2 As T) As T

    Private mReal As T
    Private mImaginary As T
    Private mult As BinaryOp
    Private add As BinaryOp
    Private convToT As Converter(Of Double, T)

    Public Sub New(ByVal real As T, ByVal imaginary As T, ByVal mult As BinaryOp, _
                   ByVal add As BinaryOp, ByVal convToT As Converter(Of Double, T))
        Me.mReal = real
        Me.mImaginary = imaginary
        Me.mult = mult
        Me.add = add
        Me.convToT = convToT
    End Sub

    Public Property Real() As T
        Get
            Return mReal
        End Get
        Set(ByVal value As T)
            mReal = value
        End Set
    End Property

    Public Property Img() As T
        Get
            Return mImaginary
        End Get
        Set(ByVal value As T)
            mImaginary = value
        End Set
    End Property

    Public ReadOnly Property Magnitude() As T
        Get
            Dim mMagnitude As Double = _
                Math.Sqrt(Convert.ToDouble(add(mult(mReal, mReal), _
                                               mult(mImaginary, mImaginary))))
            Return convToT(mMagnitude)
        End Get
    End Property
End Structure

Public Class EntryPoint
    Shared Sub Main()
        Dim c As Complex(Of Int64) = New Complex(Of Int64)(4, 5, _
                                                          AddressOf MultiplyInt64, AddressOf AddInt64, AddressOf DoubleToInt64)
        Console.WriteLine("Magnitude is {0}", c.Magnitude)
    End Sub

    Private Shared Function MultiplyInt64(ByVal val1 As Int64, _
                                          ByVal val2 As Int64) As Int64
        Return val1 * val2
    End Function

    Private Shared Function AddInt64(ByVal val1 As Int64, _
                                     ByVal val2 As Int64) As Int64
        Return val1 + val2
    End Function

    Private Shared Function DoubleToInt64(ByVal d As Double) As Int64
        Return Convert.ToInt64(d)
    End Function
End Class

这个示例会输出 Magnitude is 6 。可以看到,为了处理乘法,定义了 Complex(Of T).Multiply 委托。在构造 Complex(Of T) 时,必须传递一个引用乘法委托要调用的方法的参数,在这个例子中是 EntryPoint.MultiplyInt64 。当 Magnitude 属性需要对分量进行乘法运算时,必须使用委托而不是乘法运算符。

此外, Math.Sqrt 接受 System.Double 类型,这解释了调用 Convert.ToDouble 方法的原因。为了确保一切顺利,对 T 添加了约束,使其支持 IConvertible 接口。但还需要将 Math.Sqrt 返回的 System.Double 值转换回类型 T ,由于在编译时不知道要转换到的类型,不能依赖 System.Convert 类,因此又需要将转换操作外部化,这就是使用 Converter(Of Double, T) 委托的原因。在构造时,必须传递一个供该委托调用的方法,这里是 EntryPoint.DoubleToInt64

15.2 使 Complex(Of T) 实现 IComparable(Of T)

假设希望 Complex(Of T) 实例能够作为 SortedList(Of TKey, TValue) 泛型类型中的键值使用,那么 Complex(Of T) 需要实现 IComparable(Of T) 接口。以下是实现该接口的代码:

Imports System

Public Structure Complex(Of T As {Structure, IConvertible, IComparable})
    Implements IComparable(Of Complex(Of T))

    ' Delegate for doing multiplication.
    Public Delegate Function BinaryOp(ByVal val1 As T, ByVal val2 As T) As T

    Private mReal As T
    Private mImaginary As T
    Private mult As BinaryOp
    Private add As BinaryOp
    Private convToT As Converter(Of Double, T)

    Public Sub New(ByVal real As T, ByVal imaginary As T, ByVal mult As BinaryOp, _
                   ByVal add As BinaryOp, ByVal convToT As Converter(Of Double, T))
        Me.mReal = real
        Me.mImaginary = imaginary
        Me.mult = mult
        Me.add = add
        Me.convToT = convToT
    End Sub

    Public Property Real() As T
        Get
            Return mReal
        End Get
        Set(ByVal value As T)
            mReal = value
        End Set
    End Property

    Public Property Img() As T
        Get
            Return mImaginary
        End Get
        Set(ByVal value As T)
            mImaginary = value
        End Set
    End Property

    Public ReadOnly Property Magnitude() As T
        Get
            Dim mMagnitude As Double = _
                Math.Sqrt(Convert.ToDouble(add(mult(mReal, mReal), _
                                               mult(mImaginary, mImaginary))))
            Return convToT(mMagnitude)
        End Get
    End Property

    Public Function CompareTo(ByVal other As Complex(Of T)) As Integer _
        Implements IComparable(Of Complex(Of T)).CompareTo
        Return Magnitude.CompareTo(other.Magnitude)
    End Function
End Structure

Public Class EntryPoint
    Shared Sub Main()
        Dim c As Complex(Of Int64) = New Complex(Of Int64)(4, 5, _
                                                          AddressOf MultiplyInt64, AddressOf AddInt64, AddressOf DoubleToInt64)
        Console.WriteLine("Magnitude is {0}", c.Magnitude)
    End Sub

    Private Shared Function MultiplyInt64(ByVal val1 As Int64, _
                                          ByVal val2 As Int64) As Int64
        Return val1 * val2
    End Function

    Private Shared Function AddInt64(ByVal val1 As Int64, _
                                     ByVal val2 As Int64) As Int64
        Return val1 + val2
    End Function

    Private Shared Function DoubleToInt64(ByVal d As Double) As Int64
        Return Convert.ToInt64(d)
    End Function
End Class

这个实现将两个 Complex(Of T) 类型视为相等,如果它们具有相同的幅度。因此,大部分比较工作已经完成,但不能依赖 VB 语言的不等运算符,而是使用 CompareTo() 方法。这需要对类型 T 施加另一个约束,即它必须支持非泛型 IComparable 接口。

需要注意的是,对非泛型 IComparable 接口的约束使得 Complex(Of T) 包含泛型结构变得有点困难,因为泛型结构可能实现 IComparable(Of T) 而不是非泛型 IComparable 。实际上,根据当前的定义,不可能定义 Complex(Of Complex(Of Integer)) 类型。

为了能够从可能实现 IComparable(Of T) IComparable 甚至两者的类型构造 Complex(Of T) ,可以进行如下改进:

Imports System
Imports System.Collections.Generic

Public Structure Complex(Of T As Structure)
    Implements IComparable(Of Complex(Of T))

    ' Delegate for doing multiplication.
    Public Delegate Function BinaryOp(ByVal val1 As T, ByVal val2 As T) As T

    Private mReal As T
    Private mImaginary As T
    Private mult As BinaryOp
    Private add As BinaryOp
    Private convToT As Converter(Of Double, T)

    Public Sub New(ByVal real As T, ByVal imaginary As T, ByVal mult As BinaryOp, _
                   ByVal add As BinaryOp, ByVal convToT As Converter(Of Double, T))
        Me.mReal = real
        Me.mImaginary = imaginary
        Me.mult = mult
        Me.add = add
        Me.convToT = convToT
    End Sub

    Public Property Real() As T
        Get
            Return mReal
        End Get
        Set(ByVal value As T)
            mReal = value
        End Set
    End Property

    Public Property Img() As T
        Get
            Return mImaginary
        End Get
        Set(ByVal value As T)
            mImaginary = value
        End Set
    End Property

    Public ReadOnly Property Magnitude() As T
        Get
            Dim mMagnitude As Double = _
                Math.Sqrt(Convert.ToDouble(add(mult(mReal, mReal), _
                                               mult(mImaginary, mImaginary))))
            Return convToT(mMagnitude)
        End Get
    End Property

    Public Function CompareTo(ByVal other As Complex(Of T)) As Integer _
        Implements IComparable(Of Complex(Of T)).CompareTo
        Return Comparer(Of T).Default.Compare(Me.Magnitude, other.Magnitude)
    End Function
End Structure

Public Class EntryPoint
    Shared Sub Main()
        Dim c As Complex(Of Int64) = New Complex(Of Int64)(4, 5, _
                                                          AddressOf MultiplyInt64, AddressOf AddInt64, AddressOf DoubleToInt64)
        Console.WriteLine("Magnitude is {0}", c.Magnitude)
    End Sub

    Private Shared Sub DummyMethod(ByVal c As Complex(Of Complex(Of Integer)))
    End Sub

    Private Shared Function MultiplyInt64(ByVal val1 As Int64, _
                                          ByVal val2 As Int64) As Int64
        Return val1 * val2
    End Function

    Private Shared Function AddInt64(ByVal val1 As Int64, _
                                     ByVal val2 As Int64) As Int64
        Return val1 + val2
    End Function

    Private Shared Function DoubleToInt64(ByVal d As Double) As Int64
        Return Convert.ToInt64(d)
    End Function
End Class

在这个改进后的代码中,移除了对 T 实现 IComparable 接口的约束, CompareTo 方法依赖于 System.Collections.Generic 命名空间中定义的默认泛型比较器。同时,为了让 DummyMethod() 能够编译,移除了对 T IConvertible 约束,因为 Complex(Of T) 不实现 IConvertible ,当 T 被替换为 Complex(Of T) 时, T 也不实现 IConvertible

需要注意的是,移除 IConvertible 约束后,在 Magnitude 属性中依赖 Convert.ToDouble 方法可能会导致运行时异常,因为无法保证 T 类型实现了 IConvertible 接口。为了解决这个问题,可以在 Complex(Of T) 的构造函数中再添加一个 Convert(Of T, Double) 委托,如下所示:

Imports System
Imports System.Collections.Generic

Public Structure Complex(Of T As Structure)
    Implements IComparable(Of Complex(Of T))

    'Delegate for doing multiplication.
    Public Delegate Function BinaryOp(ByVal val1 As T, ByVal val2 As T) As T

    Private mReal As T
    Private mImaginary As T
    Private mult As BinaryOp
    Private add As BinaryOp
    Private convToT As Converter(Of Double, T)
    Private convToDouble As Converter(Of T, Double)

    Public Sub New(ByVal real As T, ByVal imaginary As T, ByVal mult As BinaryOp, _
                   ByVal add As BinaryOp, ByVal convToT As Converter(Of Double, T), _
                   ByVal convToDouble As Converter(Of T, Double))
        Me.mReal = real
        Me.mImaginary = imaginary
        Me.mult = mult
        Me.add = add
        Me.convToT = convToT
        Me.convToDouble = convToDouble
    End Sub

    Public Property Real() As T
        Get
            Return mReal

End Get
        Set(ByVal value As T)
            mReal = value
        End Set
    End Property

    Public Property Img() As T
        Get
            Return mImaginary
        End Get
        Set(ByVal value As T)
            mImaginary = value
        End Set
    End Property

    Public ReadOnly Property Magnitude() As T
        Get
            Dim mMagnitude As Double = _
                Math.Sqrt(convToDouble(add(mult(mReal, mReal), _
                                           mult(mImaginary, mImaginary))))
            Return convToT(mMagnitude)
        End Get
    End Property

    Public Function CompareTo(ByVal other As Complex(Of T)) As Integer _
        Implements IComparable(Of Complex(Of T)).CompareTo
        Return Comparer(Of T).Default.Compare(Me.Magnitude, other.Magnitude)
    End Function
End Structure

Public Class EntryPoint
    Shared Sub Main()
        Dim c As Complex(Of Int64) = New Complex(Of Int64)(4, 5, _
                                                          AddressOf MultiplyInt64, AddressOf AddInt64, AddressOf DoubleToInt64, _
                                                          AddressOf Int64ToDouble)
        Console.WriteLine("Magnitude is {0}", c.Magnitude)
    End Sub

    Private Shared Sub DummyMethod(ByVal c As Complex(Of Complex(Of Integer)))
    End Sub

    Private Shared Function MultiplyInt64(ByVal val1 As Int64, _
                                          ByVal val2 As Int64) As Int64
        Return val1 * val2
    End Function

    Private Shared Function AddInt64(ByVal val1 As Int64, _
                                     ByVal val2 As Int64) As Int64
        Return val1 + val2
    End Function

    Private Shared Function DoubleToInt64(ByVal d As Double) As Int64
        Return Convert.ToInt64(d)
    End Function

    Private Shared Function Int64ToDouble(ByVal i As Int64) As Double
        Return Convert.ToDouble(i)
    End Function
End Class

通过这种方式,`Complex(Of T)` 类型可以包含任何类型的结构,无论是泛型还是非泛型,但必须为其提供必要的手段来进行与 `Double` 类型的相互转换,以及对组成类型的乘法和加法运算。

#### 16. 动态创建构造类型
由于 CLR 的动态特性以及可以在运行时生成类和代码的事实,自然会考虑在运行时从泛型构造封闭式类型的可能性。之前的示例都是在编译时创建封闭式类型,而现在可以借助 `System.Type` 类的扩展功能来实现运行时创建。

`System.Type` 类为处理泛型提供了一些新方法,如 `GetGenericArguments()`、`GetGenericParameterConstraints()` 和 `GetGenericTypeDefinition()` 等。其中,`MakeGenericType()` 方法允许传入一个 `System.Type` 对象数组,这些对象代表要用于结果构造类型的参数列表中的类型。以下是一个使用 `MakeGenericType` 方法的示例:
```vb
Imports System
Imports System.Collections.Generic

Public Class EntryPoint
    Shared Sub Main()
        Dim intList As IList(Of Integer) = _
            CType(CreateClosedType(Of Integer)(GetType(List(Of ))), _
                  IList(Of Integer))
        Dim doubleList As IList(Of Double) = _
            CType(CreateClosedType(Of Double)(GetType(List(Of ))), _
                  IList(Of Double))
        Console.WriteLine(intList)
        Console.WriteLine(doubleList)
    End Sub

    Private Shared Function CreateClosedType(Of T)(ByVal genericType As Type) _
        As Object
        Dim typeArguments As Type() = {GetType(T)}
        Dim closedType As Type = genericType.MakeGenericType(typeArguments)
        Return Activator.CreateInstance(closedType)
    End Function
End Class

该示例的输出如下:

System.Collections.Generic.List`1[System.Int32]
System.Collections.Generic.List`1[System.Double]

CreateClosedType(Of T)() 泛型方法中,首先获取传入的泛型开放类型 List() 的引用,然后创建一个 Type 实例数组传递给 MakeGenericType() 方法,以获取封闭式类型的引用。最后,使用 System.Activator 类的 CreateInstance() 方法创建该封闭式类型的实例。

总结

泛型是 VB 和 CLR 中一项强大的特性,它为开发者带来了诸多好处。通过泛型,可以创建更高效、更安全的代码,尤其是在处理集合类型和值类型时。以下是泛型的一些主要优势总结:
| 优势 | 说明 |
| ---- | ---- |
| 效率提升 | 避免了不必要的装箱和拆箱操作,特别是在处理值类型时,显著提高了性能。 |
| 类型安全 | 编译器可以在编译时捕获许多类型相关的错误,减少运行时异常的发生。 |
| 代码复用 | 可以编写通用的代码,适用于多种不同的类型,提高了代码的复用性。 |
| 灵活性 | 支持动态创建构造类型,为开发高度动态的系统提供了可能。 |

在使用泛型时,需要注意以下几点:
- 命名约定 :遵循良好的泛型类型占位符命名约定,提高代码的可读性。
- 约束使用 :合理使用泛型约束,确保类型的正确性和安全性,但不要过度约束,以免限制代码的灵活性。
- 类型转换 :处理泛型类型转换时,需要使用自定义转换运算符或委托来实现。
- 静态数据共享 :如果需要在不同的封闭式类型之间共享静态数据,需要采用合适的方法,如单例模式。

总之,泛型为 VB 和 CLR 开发带来了巨大的价值,开发者应该充分利用这一特性,编写更优质、更高效的代码。

下面是一个简单的 mermaid 流程图,展示了泛型类型从定义到使用的基本流程:

graph LR
    A[定义泛型类型] --> B[创建构造类型]
    B --> C{是否为封闭式类型}
    C -- 是 --> D[实例化封闭式类型]
    C -- 否 --> E[继续指定类型参数]
    D --> F[使用实例]

通过这个流程图,可以清晰地看到泛型类型的使用过程,从最初的定义,到构造类型的创建,再到最终的实例化和使用。希望这篇文章能帮助你更好地理解和应用泛型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值