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[使用实例]
通过这个流程图,可以清晰地看到泛型类型的使用过程,从最初的定义,到构造类型的创建,再到最终的实例化和使用。希望这篇文章能帮助你更好地理解和应用泛型。
超级会员免费看

被折叠的 条评论
为什么被折叠?



