创建变体泛型接口(C# 和 Visual Basic)

接口中的泛型类型参数可以声明为协变参数或逆变参数。 “协变”允许接口方法具有与泛型类型参数定义的返回类型相比,派生程度更大的返回类型。 “逆变”允许接口方法具有与泛型形参指定的实参类型相比,派生程度更小的实参类型。 具有协变或逆变泛型类型参数的泛型接口称为“变体”接口。

提示

.NET Framework 4 引入了对多种现有泛型接口的变体支持。 有关 .NET Framework 中的变体接口的列表,请参见泛型接口中的变体(C# 和 Visual Basic)

声明变体泛型接口

若要声明变体泛型接口,可以对泛型类型参数使用 in 和 out 关键字。

重要说明重要事项

Visual Basic 中的 ByRef 参数和 C# 中的 ref 与 out 参数不能为变体。 值类型也不支持变体。

您可以使用 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
    
    interface ICovariant<out R>
    {
        R GetSomething();
        // The following statement generates a compiler error.
        // void SetSometing(R sampleArg);
    
    }
    

    此规则有一个例外。 如果具有用作方法参数的逆变泛型委托,则可以将类型用作该委托的泛型类型参数。 下例中的类型 R 演示了此情形。 有关更多信息,请参见委托中的变体(C# 和 Visual Basic)对 Func 和 Action 泛型委托使用变体(C# 和 Visual Basic)

    Interface ICovariant(Of Out R)
        Sub DoSomething(ByVal callback As Action(Of R))
    End Interface
    
    interface ICovariant<out R>
    {
        void DoSomething(Action<R> callback);
    }
    
  • 类型不用作接口方法的泛型约束。 下面的代码阐释了这一点。

    Interface ICovariant(Of Out R)
        ' The following statement generates a compiler error
        ' because you can use only contravariant or invariant types
        ' in generic contstraints.
        ' Sub DoSomething(Of T As R)()
    End Interface
    
    interface ICovariant<out R>
    {
        // The following statement generates a compiler error
        // because you can use only contravariant or invariant types
        // in generic contstraints.
        // void DoSomething<T>() where T : R;
    }
    

您可以使用 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 IContravariant<in A>
{
    void SetSomething(A sampleArg);
    void DoSomething<T>() where T : A;
    // The following statement generates a compiler error.
    // A GetSomething();            
}

此外还可以在同一接口中同时支持协变和逆变,但需应用于不同的类型参数,如下面的代码示例中所示。

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
interface IVariant<out R, in A>
{
    R GetSomething();
    void SetSomething(A sampleArg);
    R GetSetSometings(A sampleArg);
}

在 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
interface ICovariant<out R>
{
    R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
    public R GetSomething()
    {
        // Some code.
        return default(R);
    }
}

实现变体接口的类是固定类。 例如,考虑下面的代码。

' 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
// The interface is covariant.
ICovariant<Button> ibutton = new SampleImplementation<Button>();
ICovariant<Object> iobj = ibutton;

// The class is invariant.
SampleImplementation<Button> button = new SampleImplementation<Button>();
// The following statement generates a compiler error
// because classes are invariant.
// SampleImplementation<Object> obj = 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
interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }

尽管 IInvariant<T>(在 Visual Basic 中为 Invariant(Of T))接口和 IExtCovariant<out T>(在 Visual Basic 中为 IExtCovariant (Of Out T))接口扩展的是同一个接口,但泛型类型参数 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
interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }

但是,如果泛型类型参数 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
interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }

避免多义性

实现变体泛型接口时,变体有时可能会导致多义性。 应避免这种情况。

例如,如果在一个类中使用不同的泛型类型参数来显式实现同一变体泛型接口,便会产生多义性。 在这种情况下,编译器不会产生错误,但没有指定将在运行时选择哪个接口实现。 这可能导致代码中出现细微错误。 请考虑下面的代码示例。

提示

在 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
// Simple class hierarchy.
class Animal { }
class Cat : Animal { }
class Dog : Animal { }

// This class introduces ambiguity
// because IEnumerable<out T> is covariant.
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
{
    IEnumerator<Cat> IEnumerable<Cat>.GetEnumerator()
    {
        Console.WriteLine("Cat");
        // Some code.
        return null;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        // Some code.
        return null;
    }

    IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
    {
        Console.WriteLine("Dog");
        // Some code.
        return null;
    }
}
class Program
{
    public static void Test()
    {
        IEnumerable<Animal> pets = new Pets();
        pets.GetEnumerator();
    }
}

在此示例中,没有指定 pets.GetEnumerator 方法如何在 Cat 和 Dog 之间选择。 这可能导致代码中出现问题。

请参见

参考

对 Func 和 Action 泛型委托使用变体(C# 和 Visual Basic)

概念

泛型接口中的变体(C# 和 Visual Basic)