泛型
WARNING
泛型是"复制粘贴代码后搜索替换类型名"的语法糖。
泛型语法提供的所有功能都可以通过编写重复代码来实现。
然而这种重复既容易出错又乏味,因此泛型语法保持代码 DRY[^1]。
泛型语法引入了类型参数/类型变量,其类型值在编译时存在,而常规参数及其值仅在运行时存在。
过程、Class 和 Type(UDT)可以声明为泛型。
WARNING
泛型 Type(UDT)尚不支持成员过程(错误 TB5124)。
泛型过程
语法:
定义 ( Function | ... ) name (Of type-variable-list ) ( parameter-list ) As return-type
详细形式: ( Function | Sub | Property (Get | Let | Set) ) name (Of type-var1 [ , type-var2 ...]) ( parameter-list ) As return-typeparameter-list 可以引用任何类型变量,例如
Sub MyPrint(Of T)(ByVal file&, value As T)调用或调用点name [ (Of type-argument-list ) ] [ ( argument-list ) ]
详细形式: name [ (Of type-arg1 [ , type-arg2 ] ) ] [ ( argument-list ) ] 定义中 parameter-list 中的类型变量将被调用点 argument-list 中提供的具体类型替代,除非在 type-argument-list 中显式提供。 未在 parameter-list 中引用的类型变量必须在 type-argument-list 中作为类型参数提供。
在定义中,type-variable-list,即 (Of type-var ... ),引入泛型性。类型变量(type-var)引入任意类型的标识符,可以在以下位置引用:
- parameter-list,
- return-type,以及
- 过程体。
在调用中,type-argument-list,即 (Of type-arg ... ),根据需要可选,为那些不出现在定义 parameter-list 中的类型变量提供类型参数。在 parameter-list 中使用的类型变量将自动从调用点对应参数的类型推断出类型值,除非在 type-argument-list 中显式提供了其值。
调用点类型参数
对应可从调用参数类型推断出的类型的类型变量必须构成 type-variable-list 的尾部:
Sub MySub1(Of T, U, V)(argu As U, argv As V): End Sub
MySub1(Of Long)(33%, 42%) ' Valid: deduced U, V = Integer
MySub1(Of Long, Single)(33%, 42%) ' Valid: provided U = Single, deduced V = Integer
MySub1(Of Long, Single, Double)(33%, 42%)' Valid: provided U = Single, provided V = Double
MySub1(Of Long, , Double)(33%, 42%) ' Invalid: omitted deduced type must be trailing因此,要禁止推断,将类型变量放在类型列表中不可推断的类型参数之前:
' T must be provided, it won't be deduced
Function MyFn1(Of T, U)(argu As T) As U: End Function
MyFn1(Of Single, String)(10%) ' Valid: provided T = Single, U = String
MyFn1(Of, String)(10%) ' Invalid: T is not trailing so it can't be omitted
' Effectively, the definition of MyFn1
' suppresses deduction of T只有未使用的类型变量可以在 type-variable-list 中第一个位置之后省略其参数:
Sub MySub2(Of T, U, V)(argt As T, argv As V): End Sub
Sub MySub3(Of U, V)(argv As V): End Sub
MySub2(Of Single, , Double)(1%, 2%) ' Valid: unused U can be omitted as it's not the first
' in the type-parameter-list
MySub3(Of, Single)(22%) ' Invalid: unused U can't be omitted as it's the first
' variable in the type-parameter-list示例 1
在此示例中,泛型 First 和 Last Sub 的调用不需要显式提供类型参数值(即 (Of ... )),因为它们可以从参数类型推断。
Public Function First(Of T)(Array() As T) As T
If IsArrayInitialized(Array) Then Return Array(LBound(Array))
End Function
Public Function Last(Of T)(Array() As T) As T
If IsArrayInitialized(Array) Then Return Array(UBound(Array))
End Function
Sub Test()
Dim data() As String = Array("A", "B", "C")
Debug.Assert First(data) = "A"
Debug.Assert Last(data) = "C"
End Sub如果没有泛型语法,过程必须为每个使用的类型 T 单独编写。在下面的示例中,需要 T=String 和 T=Integer:
Public Function First(Array() As String) As String
If IsArrayInitialized(Array) Then Return Array(LBound(Array))
End Function
Public Function First(Array() As Integer) As Integer
If IsArrayInitialized(Array) Then Return Array(UBound(Array))
End Function
Sub Test()
Dim strings() As String = Array("A", "B", "C")
Dim ints() As Integer = Array(1, 2, 3)
Debug.Assert First(strings) = "A" AndAlso First(ints) = 1
End Sub示例 2:部分类型变量不出现在 parameter-list 中
类型变量可能不出现在 parameter-list 中的常见情况有两种:
- 当它是返回类型时,和/或
- 当它在过程体中使用时。
以下示例说明了这些情况:
Public Function Caster(Of R, U, T)(value As T) As R
Dim intermediate As U = CType(Of U)(value)
Return CType(Of R)(intermediate)
End Function
Sub Test()
' Type T is deduced to be Single, from the argument 1.23!
Debug.Assert Example(Of String, Integer)(1.23!) = "1"
' Type T is explicitly provided as Double. The argument is cast to that type.
Debug.Print Example(Of String, Integer, Double)(1.23!) = "1"
End Sub函数 Caster 在其作用域内引入了三个类型变量:
- T 默认从 value 参数的类型推断,或在调用时提供,
- R 是返回类型,必须在调用时提供,
- U 是函数体中使用的类型,必须在调用时提供。
TIP
定义中类型变量的顺序可以安排为尾部变量在 parameter-list 中使用。这样如果调用点从参数类型推断的类型合适,这些类型变量的类型值可以省略。
在调用
Example(Of String, Integer)(1.23!)中, T 被推断为 Single,U 被提供并设为 Integer,R 被提供并设为 String。在调用
Example(Of String, Integer, Double)(1.23!)中, T 被提供并设为 Double,U 被提供并设为 Integer,R 被提供并设为 String。- 首先,编译器将
1.23!转换为形参的类型,即 Double1.23#。 - 然后,在函数体中,value 在赋值给 intermediate 时被转换为 Integer。
- 最后,同样在函数体中,intermediate 被转换为结果类型 String 并返回。
- 首先,编译器将
泛型类和 UDT
语法:
- 定义 [ Class | ... ] name (Of type-variable-list )
- 详细形式: [ Class | Type ] name (Of type-var1 [ , type-var2 ... ] )
- 实例化name (Of type-argument-list )
- 详细形式: name (Of type-arg1 [ , type-arg2 ... ] )
类型变量(type-var)引入任意类型的标识符,可以在类体内的任何位置引用。
WARNING
实例化泛型类和 UDT 时,所有类型参数都必须提供。
如果未提供,可能会导致代码生成错误和运行时的静默失败。
正确和不正确实例化的示例
Class MyClass(Of T, U)
Function DumpT%(value As T): Debug.Print value: End Function
Function DumpU%(value As U): Debug.Print value: End Function
End Class
Dim i As New MyClass(Of Integer) ' Invalid, U is not provided, silent error
i.DumpT(12) ' Valid, uses T = Integer
i.DumpU(12) ' Invalid, uses undefined U, causes a codegen/silent error
Dim j As New MyClass(Of Integer, Single) ' Correct instantiation
j.DumpU(12) ' Valid, uses U = Single类型实例与对象实例
泛型类允许用实例化时提供的类型参数替换类型变量。每次使用泛型类名加类型参数都会将泛型类类型实例化为一个常规类类型。
INFO
编译时:通过调用类名加参数来实例化泛型类。
运行时:可以创建那些实例化类型的对象。
在下面的示例中,实例化了两个类类型:MyClass(Integer) 和 MyClass(String)。这发生在编译时。运行时没有创建 MyClass 的实例,因为两个变量都默认为 Nothing:
Class MyClass(Of T) ' ...
Sub Test()
Dim intVar As MyClass(Integer)
Dim strVar As MyClass(String)
Debug.Assert intVar Is Nothing AndAlso strVar Is Nothing
End SubList 类示例
泛型类允许在整个类的方法中使用类型参数。以下示例展示了创建一个泛型 List 类:
[COMCreatable(False)]
Class List(Of T)
Private mData() As T
Sub New(preset() As T)
mData = preset
End Sub
[DefaultMember]
Function GetAt(ByVal index&) As T
Return mData(index)
End Function
End Class
Sub Test()
Dim li As Any = New List(Of Integer)(Array(5, 6, 7))
Debug.Assert li(0) = 5 AndAlso li(2) = 7
End SubList UDT 示例
虽然 twinBASIC 中泛型 UDT 尚不支持成员过程,但数据成员是支持的:
Type ListU(Of T)
value() As T
End Type
Sub Test()
Dim lu As ListU(Of Long)
ReDim lu.value(10)
lu.value(0) = 5
End Sub[^1]: DRY = Don't Repeat Yourself