Создание вариативных универсальных интерфейсов (C# и Visual Basic)

Параметры универсального типа можно объявлять в интерфейсах в качестве ковариантных или контравариантных. Ковариация позволяет методам интерфейса иметь более производные типы возвращаемых значений, чем указано параметрами универсального типа. Контравариация позволяет методам интерфейса иметь менее производные типы аргументов, чем указано параметрами универсального типа. Универсальный интерфейс, который имеет ковариантные или контравариантные параметры универсальных типов, называется вариативным.

Примечание

В платформе .NET Framework 4 появилась поддержка вариативности для нескольких существующих универсальных интерфейсов.Список вариативных интерфейсов в платформе .NET Framework см. в разделе Вариативность в универсальных интерфейсах (C# и Visual Basic).

Объявление вариативных универсальных интерфейсов

Для объявления вариативных универсальных интерфейсов необходимо использовать ключевые слова in и out для параметров универсального типа.

Важно!

Параметры ByRef в Visual Basic, а также параметры ref и out в C# не могут быть вариативными.Типы значений также не поддерживают вариативность.

Для объявления ковариантного параметра универсального типа необходимо использовать ключевое слово 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> (Invariant(Of T) в Visual Basic) параметр универсального типа T является инвариантным, тогда как в IExtCovariant<out T> (IExtCovariant (Of Out T) в Visual Basic) параметр типа является ковариантным, хотя оба интерфейса расширяют один и тот же интерфейс. Такое же правило применяется к контравариантным параметрам универсального типа.

Можно создать интерфейс, который расширяет и интерфейс, в котором параметр универсального типа 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)