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

В платформе .NET Framework 4 появилась поддержка вариативности для нескольких существующих универсальных интерфейсов. Поддержка вариативности позволяет выполнять неявное преобразование классов, которые реализуют эти интерфейсы. В настоящее время вариативными являются следующие интерфейсы.

  • IEnumerable (T является ковариантным интерфейсом)

  • IEnumerator (T является ковариантным интерфейсом)

  • IQueryable (T является ковариантным интерфейсом)

  • IGrouping (TKey и TElement являются ковариантными интерфейсами)

  • IComparer (T является контравариантным интерфейсом)

  • IEqualityComparer (T является контравариантным интерфейсом)

  • IComparable (T является контравариантным интерфейсом)

Ковариация позволяет методу иметь тип возвращаемого значения, степень наследования которого больше, чем указано в параметре универсального типа интерфейса. Чтобы продемонстрировать функцию ковариации, рассмотрим следующие универсальные интерфейсы: IEnumerable<Object> и IEnumerable<String> (IEnumerable(Of Object) и IEnumerable(Of String) в Visual Basic). Интерфейс IEnumerable<String> (IEnumerable(Of String) в Visual Basic) не наследует интерфейс IEnumerable<Object> (IEnumerable(Of Object) в Visual Basic). При этом тип String наследует тип Object и в некоторых случаях может потребоваться назначение объектов этих интерфейсов друг другу. Это продемонстрировано в приведенном ниже примере кода.

Dim strings As IEnumerable(Of String) = New List(Of String)
Dim objects As IEnumerable(Of Object) = strings
IEnumerable<String> strings = new List<String>();
IEnumerable<Object> objects = strings;

В более ранних версиях .NET Framework этот код приводит к ошибке компиляции в C# и в Visual Basic при указании Option Strict On. Теперь же можно использовать strings вместо objects, как показано в предыдущем примере, поскольку интерфейс IEnumerable является ковариантным.

Контравариация позволяет методу иметь типы аргументов, степень наследования которых меньше, чем указано в параметре универсального типа интерфейса. Чтобы продемонстрировать функцию контравариации, предположим, что создан класс BaseComparer для сравнения экземпляров класса BaseClass. Класс BaseComparer реализует интерфейс IEqualityComparer<BaseClass> (IEqualityComparer(Of BaseClass) в Visual Basic). Поскольку теперь интерфейс IEqualityComparer является контравариантным, можно использовать класс BaseComparer для сравнения экземпляров классов, которые наследуют класс BaseClass. Это продемонстрировано в приведенном ниже примере кода.

' Simple hierarchy of classes. 
Class BaseClass
End Class 

Class DerivedClass
    Inherits BaseClass
End Class 

' Comparer class. 
Class BaseComparer
    Implements IEqualityComparer(Of BaseClass)

    Public Function Equals1(ByVal x As BaseClass,
                            ByVal y As BaseClass) As Boolean _
                            Implements IEqualityComparer(Of BaseClass).Equals
        Return (x.Equals(y))
    End Function 

    Public Function GetHashCode1(ByVal obj As BaseClass) As Integer _
        Implements IEqualityComparer(Of BaseClass).GetHashCode
        Return obj.GetHashCode
    End Function 
End Class 
Sub Test()
    Dim baseComparer As IEqualityComparer(Of BaseClass) = New BaseComparer
    ' Implicit conversion of IEqualityComparer(Of BaseClass) to  
    ' IEqualityComparer(Of DerivedClass). 
    Dim childComparer As IEqualityComparer(Of DerivedClass) = baseComparer
End Sub
// Simple hierarchy of classes. 
class BaseClass { }
class DerivedClass : BaseClass { }

// Comparer class. 
class BaseComparer : IEqualityComparer<BaseClass> 
{
    public int GetHashCode(BaseClass baseInstance)
    {
        return baseInstance.GetHashCode();
    }
    public bool Equals(BaseClass x, BaseClass y)
    {
        return x == y;
    }
}
class Program
{
    static void Test()
    {
        IEqualityComparer<BaseClass> baseComparer = new BaseComparer();

        // Implicit conversion of IEqualityComparer<BaseClass> to  
        // IEqualityComparer<DerivedClass>.
        IEqualityComparer<DerivedClass> childComparer = baseComparer;
    }
}

Дополнительные примеры см. в разделе Использование вариативности в интерфейсах для универсальных коллекций (C# и Visual Basic).

Вариативность в универсальных интерфейсах поддерживается только для ссылочных типов. Типы значений не поддерживают вариативность. Например, IEnumerable<int> (IEnumerable(Of Integer) в Visual Basic) нельзя неявно преобразовать в IEnumerable<object> (IEnumerable(Of Object) в Visual Basic), поскольку тип значения — integer.

Dim integers As IEnumerable(Of Integer) = New List(Of Integer)
' The following statement generates a compiler error 
' with Option Strict On, because Integer is a value type. 
' Dim objects As IEnumerable(Of Object) = integers
IEnumerable<int> integers = new List<int>();
// The following statement generates a compiler errror, 
// because int is a value type. 
// IEnumerable<Object> objects = integers;

Кроме того, важно помнить, что классы, которые реализуют вариативные интерфейсы, сами являются инвариантными. Например, несмотря на то, что List реализует ковариантный интерфейс IEnumerable, нельзя неявно преобразовать List<Object> в List<String> (List(Of Object) в List(Of String) в Visual Basic). Это показано в следующем примере кода.

' The following statement generates a compiler error 
' because classes are invariant. 
' Dim list As List(Of Object) = New List(Of String) 

' You can use the interface object instead. 
Dim listObjects As IEnumerable(Of Object) = New List(Of String)
// The following line generates a compiler error 
// because classes are invariant. 
// List<Object> list = new List<String>(); 

// You can use the interface object instead.
IEnumerable<Object> listObjects = new List<String>();

См. также

Ссылки

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

Основные понятия

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

Универсальные интерфейсы

Вариативность в делегатах (C# и Visual Basic)