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

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

Начиная с .NET Framework 4 вариативными являются следующие интерфейсы:

Начиная с .NET Framework 4.5, вариативными являются следующие интерфейсы:

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

IEnumerable<String> strings = new List<String>();
IEnumerable<Object> objects = strings;

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

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

// 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#).

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

IEnumerable<int> integers = new List<int>();
// The following statement generates a compiler error,
// because int is a value type.
// IEnumerable<Object> objects = integers;

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

// 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>();

См. также