可以在接口中将泛型类型参数声明为协变或逆变。 协变允许接口方法具有与泛型类型参数定义的返回类型相比,派生程度更大的返回类型。 逆变允许接口方法具有与泛型形参指定的实参类型相比,派生程度更小的实参类型。 具有协变或逆变泛型类型参数的泛型接口称为 变体。
注释
.NET Framework 4 引入了对多个现有泛型接口的方差支持。 有关 .NET Framework 中变体接口的列表,请参阅泛型接口中的变体(Visual Basic)。
声明变体泛型接口
可以使用关键字in
和out
为泛型类型参数声明变体泛型接口。
重要
ByRef
Visual Basic 中的参数不能是变体。 值类型也不支持差异。
可以使用关键字声明泛型类型参数协变 out
。 协变类型必须满足以下条件:
该类型仅用作接口方法的返回类型,而不用作方法参数的类型。 以下示例对此进行了说明,在该示例中,类型
R
声明为协变。Interface ICovariant(Of Out R) Function GetSomething() As R ' The following statement generates a compiler error. ' Sub SetSomething(ByVal sampleArg As R) End Interface
此规则有一个例外。 如果有逆变泛型委托作为方法参数,则可以将该类型用作该委托的泛型类型参数。 下面的示例中的类型
R
对此进行了说明。 有关详细信息,请参阅委托中的协变和逆变(Visual Basic)和在 Func 和 Action 泛型委托中使用协变和逆变(Visual Basic)。Interface ICovariant(Of Out R) Sub DoSomething(ByVal callback As Action(Of R)) End Interface
该类型不用作接口方法的泛型约束。 以下代码对此进行了说明。
Interface ICovariant(Of Out R) ' The following statement generates a compiler error ' because you can use only contravariant or invariant types ' in generic constraints. ' Sub DoSomething(Of T As R)() End Interface
可以使用关键字声明泛型类型参数逆变 in
。 逆变量类型只能用作方法参数的类型,不能用作接口方法的返回类型。 逆变类型也可用于泛型约束。 以下代码演示如何声明逆变接口,并对其方法之一使用泛型约束。
Interface IContravariant(Of In A)
Sub SetSomething(ByVal sampleArg As A)
Sub DoSomething(Of T As A)()
' The following statement generates a compiler error.
' Function GetSomething() As A
End Interface
也可以在同一接口中同时支持协变和逆变,但对于不同的类型参数,如以下代码示例所示。
Interface IVariant(Of Out R, In A)
Function GetSomething() As R
Sub SetSomething(ByVal sampleArg As A)
Function GetSetSomething(ByVal sampleArg As A) As R
End Interface
在 Visual Basic 中,不能在变体接口中声明事件,而无需指定委托类型。 此外,变体接口不能有嵌套类、枚举或结构,但它可以有嵌套接口。 以下代码对此进行了说明。
Interface ICovariant(Of Out R)
' The following statement generates a compiler error.
' Event SampleEvent()
' The following statement specifies the delegate type and
' does not generate an error.
Event AnotherEvent As EventHandler
' The following statements generate compiler errors,
' because a variant interface cannot have
' nested enums, classes, or structures.
'Enum SampleEnum : test : End Enum
'Class SampleClass : End Class
'Structure SampleStructure : Dim value As Integer : End Structure
' Variant interfaces can have nested interfaces.
Interface INested : End Interface
End Interface
实现变体泛型接口
通过使用用于固定接口的相同语法,在类中实现变体泛型接口。 下面的代码示例演示如何在泛型类中实现协变接口。
Interface ICovariant(Of Out R)
Function GetSomething() As R
End Interface
Class SampleImplementation(Of R)
Implements ICovariant(Of R)
Public Function GetSomething() As R _
Implements ICovariant(Of R).GetSomething
' Some code.
End Function
End Class
实现变体接口的类是固定的。 例如,请考虑以下代码。
The interface is covariant.
Dim ibutton As ICovariant(Of Button) =
New SampleImplementation(Of Button)
Dim iobj As ICovariant(Of Object) = ibutton
' The class is invariant.
Dim button As SampleImplementation(Of Button) =
New SampleImplementation(Of Button)
' The following statement generates a compiler error
' because classes are invariant.
' Dim obj As SampleImplementation(Of Object) = button
扩展变体泛型接口
扩展变体泛型接口时,必须使用 in
和 out
关键字显式指定派生接口是否支持变体。 编译器不会根据正在扩展的接口来推断变体。 例如,请考虑以下接口。
Interface ICovariant(Of Out T)
End Interface
Interface IInvariant(Of T)
Inherits ICovariant(Of T)
End Interface
Interface IExtCovariant(Of Out T)
Inherits ICovariant(Of T)
End Interface
在接口中 Invariant(Of T)
,泛型类型参数 T
是固定的,而在类型参数中 IExtCovariant (Of Out T)
是协变的,尽管这两个接口都扩展了相同的接口。 同一规则应用于逆变泛型类型参数。
无论泛型类型参数 T
在接口中是协变还是逆变,都可以创建一个接口来扩展这两类接口,只要在扩展接口中,该 T
泛型类型参数为固定参数。 下面的代码示例对此进行了说明。
Interface ICovariant(Of Out T)
End Interface
Interface IContravariant(Of In T)
End Interface
Interface IInvariant(Of T)
Inherits ICovariant(Of T), IContravariant(Of T)
End Interface
但是,如果在一个接口中声明泛型类型参数 T
,则不能在扩展接口中声明该参数是逆变的,反之亦然。 下面的代码示例对此进行了说明。
Interface ICovariant(Of Out T)
End Interface
' The following statements generate a compiler error.
' Interface ICoContraVariant(Of In T)
' Inherits ICovariant(Of T)
' End Interface
避免歧义
实现变体泛型接口时,变体有时可能会导致歧义。 应避免这种情况。
例如,如果在一个类中显式实现具有相同泛型类型参数的同一变体泛型接口,则可以创建歧义。 在这种情况下,编译器不会生成错误,但未指定将在运行时选择哪个接口实现。 这可能会导致代码中的细微错误。 请考虑以下代码示例。
注释
借助 Option Strict Off
,Visual Basic 会在存在模棱两可的接口实现时生成编译器警告。 使用 Option Strict On
,Visual Basic 将生成编译器错误。
' Simple class hierarchy.
Class Animal
End Class
Class Cat
Inherits Animal
End Class
Class Dog
Inherits Animal
End Class
' This class introduces ambiguity
' because IEnumerable(Of Out T) is covariant.
Class Pets
Implements IEnumerable(Of Cat), IEnumerable(Of Dog)
Public Function GetEnumerator() As IEnumerator(Of Cat) _
Implements IEnumerable(Of Cat).GetEnumerator
Console.WriteLine("Cat")
' Some code.
End Function
Public Function GetEnumerator1() As IEnumerator(Of Dog) _
Implements IEnumerable(Of Dog).GetEnumerator
Console.WriteLine("Dog")
' Some code.
End Function
Public Function GetEnumerator2() As IEnumerator _
Implements IEnumerable.GetEnumerator
' Some code.
End Function
End Class
Sub Main()
Dim pets As IEnumerable(Of Animal) = New Pets()
pets.GetEnumerator()
End Sub
在此示例中,未指定方法如何在 pets.GetEnumerator
和 Cat
之间进行选择Dog
。 这可能会导致代码中出现问题。