Вариативность в универсальных интерфейсах (C#)
В платформе .NET Framework 4 появилась поддержка вариативности для нескольких существующих универсальных интерфейсов. Поддержка вариативности позволяет выполнять неявное преобразование классов, реализующих эти интерфейсы.
Начиная с .NET Framework 4 вариативными являются следующие интерфейсы:
IEnumerable<T> (T является ковариантным)
IEnumerator<T> (T является ковариантным)
IQueryable<T> (T является ковариантным)
IGrouping<TKey,TElement> (
TKey
иTElement
являются ковариантными)IComparer<T> (T является контравариантным)
IEqualityComparer<T> (T является контравариантным)
IComparable<T> (T является контравариантным)
Начиная с .NET Framework 4.5, вариативными являются следующие интерфейсы:
IReadOnlyList<T> (T является ковариантным)
IReadOnlyCollection<T> (T является ковариантным)
Ковариация позволяет методу иметь тип возвращаемого значения, степень наследования которого больше, чем указано в параметре универсального типа интерфейса. Чтобы продемонстрировать функцию ковариации, рассмотрим следующие универсальные интерфейсы: 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>();