Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Ковариантность и контравариантность — это термины, которые ссылаются на возможность использования более производного типа (более конкретного) или менее производного типа (менее конкретного), чем первоначально указано. Параметры универсального типа поддерживают ковариацию и контравариантность, чтобы обеспечить большую гибкость при назначении и использовании универсальных типов.
При обращении к системе типов, ковариации, контравариации и инвариантности имеются следующие определения. В примерах предполагается наличие базового класса с именем Base
и производного класса с именем Derived
.
Covariance
Позволяет использовать более производный тип, чем первоначально указанный.
Вы можете назначить экземпляр
IEnumerable<Derived>
переменной типаIEnumerable<Base>
.Contravariance
Позволяет использовать более универсальный (менее производный) тип, чем первоначально указанный.
Вы можете назначить экземпляр
Action<Base>
переменной типаAction<Derived>
.Invariance
Означает, что вы можете использовать только исходный тип. Параметр инвариантного универсального типа не является ковариантным или контравариантным.
Невозможно присвоить экземпляр
List<Base>
переменной типаList<Derived>
и наоборот.
Параметры типа Covariant позволяют создавать назначения, которые выглядят так же, как обычный полиморфизм, как показано в следующем коде.
IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
Dim d As IEnumerable(Of Derived) = New List(Of Derived)
Dim b As IEnumerable(Of Base) = d
Класс List<T> реализует IEnumerable<T> интерфейс, поэтому List<Derived>
(List(Of Derived)
в Visual Basic) реализует IEnumerable<Derived>
. Параметр ковариантного типа выполняет остальные действия.
Контравариантность, с другой стороны, кажется контринтуитивной. В следующем примере создается делегат типа Action<Base>
(Action(Of Base)
в Visual Basic), а затем назначается делегат переменной типа Action<Derived>
.
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());
Dim b As Action(Of Base) = Sub(target As Base)
Console.WriteLine(target.GetType().Name)
End Sub
Dim d As Action(Of Derived) = b
d(New Derived())
Это выглядит странно, но это типобезопасный код, который компилируется и выполняется. Лямбда-выражение соответствует назначенному делегату, поэтому он определяет метод, который принимает один параметр типа Base
и не имеет возвращаемого значения. Результирующий делегат может быть назначен переменной с типом Action<Derived>
, потому что параметр типа T
делегата Action<T> является контравариантным. Код является типобезопасным, поскольку T
задает тип параметра. При вызове делегата типа Action<Base>
, как если бы он был делегатом типа Action<Derived>
, его аргумент должен иметь тип Derived
. Этот аргумент всегда можно безопасно передать базовому методу, так как параметр метода имеет тип Base
.
Как правило, параметр ковариантного типа можно использовать в качестве возвращаемого типа делегата, а параметры контравариантного типа можно использовать в качестве типов параметров. Для интерфейса параметры ковариантного типа можно использовать в качестве возвращаемых типов методов интерфейса, а параметры контравариантного типа можно использовать в качестве типов параметров методов интерфейса.
Ковариантность и контравариантность коллективно называются вариантностью. Параметр универсального типа, не помеченный ковариантной или контравариантной, называется инвариантным. Краткое описание фактов о ковариантности в общем языке выполнения (CLR):
Параметры типа variant ограничены универсальным интерфейсом и универсальными типами делегатов.
Универсальный интерфейс или универсальный тип делегата может иметь как ковариантные, так и контравариантные параметры типа.
Вариативность применяется только к ссылочным типам; Если указать тип значения для параметра типа variant, этот параметр типа является инвариантным для результирующего созданного типа.
Вариативность не применяется к сочетанию делегатов. То есть, учитывая два делегата типов
Action<Derived>
иAction<Base>
(Action(Of Derived)
иAction(Of Base)
в Visual Basic), нельзя объединить второй делегат с первым, хотя результат будет типобезопасным. Вариативность позволяет назначить второй делегат переменной типаAction<Derived>
, но делегаты могут объединяться только в том случае, если их типы совпадают точно.Начиная с C# 9 поддерживаются ковариантные типы возвращаемых значений. Переопределяющий метод может объявлять более производный возвращаемый тип метода, который он переопределяет, а переопределяющее, доступное только для чтения свойство также может объявлять более производный тип.
Универсальные интерфейсы с параметрами ковариантного типа
Несколько универсальных интерфейсов имеют параметры ковариантного типа, например , IEnumerable<T>, IEnumerator<T>IQueryable<T>и IGrouping<TKey,TElement>. Все параметры типа этих интерфейсов являются ковариантными, поэтому параметры типа используются только для возвращаемых типов элементов.
В следующем примере показаны параметры ковариантного типа. В примере определены два типа: Base
имеет статический метод с именем PrintBases
, который принимает IEnumerable<Base>
(IEnumerable(Of Base)
в Visual Basic) и выводит элементы.
Derived
наследует от Base
. В примере создается пустой List<Derived>
(List(Of Derived)
в Visual Basic) и демонстрируется, что этот тип можно передать PrintBases
и присвоить переменной типа IEnumerable<Base>
без приведения.
List<T> реализует IEnumerable<T>, который имеет один параметр ковариантного типа. Параметр ковариантного типа является причиной того, почему экземпляр IEnumerable<Derived>
может использоваться вместо IEnumerable<Base>
.
using System;
using System.Collections.Generic;
class Base
{
public static void PrintBases(IEnumerable<Base> bases)
{
foreach(Base b in bases)
{
Console.WriteLine(b);
}
}
}
class Derived : Base
{
public static void Main()
{
List<Derived> dlist = new List<Derived>();
Derived.PrintBases(dlist);
IEnumerable<Base> bIEnum = dlist;
}
}
Imports System.Collections.Generic
Class Base
Public Shared Sub PrintBases(ByVal bases As IEnumerable(Of Base))
For Each b As Base In bases
Console.WriteLine(b)
Next
End Sub
End Class
Class Derived
Inherits Base
Shared Sub Main()
Dim dlist As New List(Of Derived)()
Derived.PrintBases(dlist)
Dim bIEnum As IEnumerable(Of Base) = dlist
End Sub
End Class
Универсальные интерфейсы с параметрами контравариантного типа
Несколько универсальных интерфейсов имеют параметры контравариантного типа; например, IComparer<T>, IComparable<T>и IEqualityComparer<T>. Эти интерфейсы имеют только контравариантные параметры типа, поэтому параметры типа используются только в качестве типов параметров в членах интерфейсов.
В следующем примере показаны параметры контравариантного типа. В примере определяется абстрактный класс (MustInherit
в Visual Basic) Shape
со свойством Area
. В примере также определяется ShapeAreaComparer
класс, реализующий IComparer<Shape>
(IComparer(Of Shape)
в Visual Basic). Реализация IComparer<T>.Compare метода основана на значении Area
свойства, поэтому ShapeAreaComparer
можно использовать для сортировки Shape
объектов по областям.
Класс Circle
наследует Shape
и переопределяет Area
. В примере создается SortedSet<T> из Circle
объектов с помощью конструктора, который принимает IComparer<Circle>
(IComparer(Of Circle)
в Visual Basic). Однако вместо передачи объекта IComparer<Circle>
, передается объект ShapeAreaComparer
, который реализует IComparer<Shape>
. Пример может использовать компаратор менее производного типа (Shape
), когда код требует компаратора более производного типа (Circle
), так как параметр типа универсального интерфейса IComparer<T> является контравариантным.
При добавлении нового объекта Circle
в SortedSet<Circle>
, каждый раз, когда новый элемент сравнивается с существующим элементом, вызывается метод IComparer<Shape>.Compare
(IComparer(Of Shape).Compare
метод в Visual Basic) объекта ShapeAreaComparer
. Тип параметра метода (Shape
) менее производный, чем передаваемый тип (Circle
), поэтому вызов безопасный с точки зрения типов. Контравариантность позволяет ShapeAreaComparer
сортировать коллекцию любого одного типа, а также смешанный набор типов, производных от Shape
.
using System;
using System.Collections.Generic;
abstract class Shape
{
public virtual double Area { get { return 0; }}
}
class Circle : Shape
{
private double r;
public Circle(double radius) { r = radius; }
public double Radius { get { return r; }}
public override double Area { get { return Math.PI * r * r; }}
}
class ShapeAreaComparer : System.Collections.Generic.IComparer<Shape>
{
int IComparer<Shape>.Compare(Shape a, Shape b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : a.Area.CompareTo(b.Area);
}
}
class Program
{
static void Main()
{
// You can pass ShapeAreaComparer, which implements IComparer<Shape>,
// even though the constructor for SortedSet<Circle> expects
// IComparer<Circle>, because type parameter T of IComparer<T> is
// contravariant.
SortedSet<Circle> circlesByArea =
new SortedSet<Circle>(new ShapeAreaComparer())
{ new Circle(7.2), new Circle(100), null, new Circle(.01) };
foreach (Circle c in circlesByArea)
{
Console.WriteLine(c == null ? "null" : "Circle with area " + c.Area);
}
}
}
/* This code example produces the following output:
null
Circle with area 0.000314159265358979
Circle with area 162.860163162095
Circle with area 31415.9265358979
*/
Imports System.Collections.Generic
MustInherit Class Shape
Public MustOverride ReadOnly Property Area As Double
End Class
Class Circle
Inherits Shape
Private r As Double
Public Sub New(ByVal radius As Double)
r = radius
End Sub
Public ReadOnly Property Radius As Double
Get
Return r
End Get
End Property
Public Overrides ReadOnly Property Area As Double
Get
Return Math.Pi * r * r
End Get
End Property
End Class
Class ShapeAreaComparer
Implements System.Collections.Generic.IComparer(Of Shape)
Private Function AreaComparer(ByVal a As Shape, ByVal b As Shape) As Integer _
Implements System.Collections.Generic.IComparer(Of Shape).Compare
If a Is Nothing Then Return If(b Is Nothing, 0, -1)
Return If(b Is Nothing, 1, a.Area.CompareTo(b.Area))
End Function
End Class
Class Program
Shared Sub Main()
' You can pass ShapeAreaComparer, which implements IComparer(Of Shape),
' even though the constructor for SortedSet(Of Circle) expects
' IComparer(Of Circle), because type parameter T of IComparer(Of T)
' is contravariant.
Dim circlesByArea As New SortedSet(Of Circle)(New ShapeAreaComparer()) _
From {New Circle(7.2), New Circle(100), Nothing, New Circle(.01)}
For Each c As Circle In circlesByArea
Console.WriteLine(If(c Is Nothing, "Nothing", "Circle with area " & c.Area))
Next
End Sub
End Class
' This code example produces the following output:
'
'Nothing
'Circle with area 0.000314159265358979
'Circle with area 162.860163162095
'Circle with area 31415.9265358979
Универсальные делегаты с параметрами типа variant
Универсальные Func
делегаты, например Func<T,TResult>, имеют ковариантные возвращаемые типы и контравариантные типы параметров. Универсальные Action
делегаты, например Action<T1,T2>, имеют контравариантные типы параметров. Это означает, что делегаты могут быть назначены переменным с более производными типами параметров и (в случае Func
универсальных делегатов) менее производными типами возвращаемых значений.
Замечание
Последний параметр типа обобщённых делегатов Func
указывает тип возвращаемого значения в сигнатуре делегата. Это ковариант (out
ключевое слово), в то время как другие параметры универсального типа являются контравариантными (in
ключевым словом).
Приведенный ниже код иллюстрирует это. Первый фрагмент кода определяет класс с именем Base
, класс с именем, который Derived
наследует Base
, и другой класс с методом static
(Shared
в Visual Basic) с именем MyMethod
. Метод принимает экземпляр Base
и возвращает экземпляр Derived
. (Если аргумент является экземпляром Derived
, MyMethod
возвращает его; если аргумент является экземпляром Base
, MyMethod
возвращает новый экземпляр Derived
.) В Main()
примере создается экземпляр Func<Base, Derived>
(Func(Of Base, Derived)
в Visual Basic), который представляет MyMethod
и сохраняет его в переменной f1
.
public class Base {}
public class Derived : Base {}
public class Program
{
public static Derived MyMethod(Base b)
{
return b as Derived ?? new Derived();
}
static void Main()
{
Func<Base, Derived> f1 = MyMethod;
Public Class Base
End Class
Public Class Derived
Inherits Base
End Class
Public Class Program
Public Shared Function MyMethod(ByVal b As Base) As Derived
Return If(TypeOf b Is Derived, b, New Derived())
End Function
Shared Sub Main()
Dim f1 As Func(Of Base, Derived) = AddressOf MyMethod
Второй фрагмент кода показывает, что делегат может быть назначен переменной типа Func<Base, Base>
(Func(Of Base, Base)
в Visual Basic), так как возвращаемый тип является ковариантным.
// Covariant return type.
Func<Base, Base> f2 = f1;
Base b2 = f2(new Base());
' Covariant return type.
Dim f2 As Func(Of Base, Base) = f1
Dim b2 As Base = f2(New Base())
Третий фрагмент кода показывает, что делегат может быть назначен переменной типа Func<Derived, Derived>
(Func(Of Derived, Derived)
в Visual Basic), так как тип параметра является контравариантным.
// Contravariant parameter type.
Func<Derived, Derived> f3 = f1;
Derived d3 = f3(new Derived());
' Contravariant parameter type.
Dim f3 As Func(Of Derived, Derived) = f1
Dim d3 As Derived = f3(New Derived())
Последний фрагмент кода показывает, что делегат может быть назначен переменной типа Func<Derived, Base>
(Func(Of Derived, Base)
в Visual Basic), сочетая эффекты типа контравариантного параметра и ковариантного возвращаемого типа.
// Covariant return type and contravariant parameter type.
Func<Derived, Base> f4 = f1;
Base b4 = f4(new Derived());
' Covariant return type and contravariant parameter type.
Dim f4 As Func(Of Derived, Base) = f1
Dim b4 As Base = f4(New Derived())
Вариация в негенерических делегатах
В приведенном выше коде подпись точно соответствует сигнатуре MyMethod
созданного универсального делегата: Func<Base, Derived>
(Func(Of Base, Derived)
в Visual Basic). В примере показано, что этот универсальный делегат может храниться в переменных или параметрах метода, имеющих более производные типы параметров и менее производные типы возвращаемых данных, если все типы делегатов создаются из универсального типа Func<T,TResult>делегата.
Это важная точка. Эффекты ковариации и контравариации в параметрах типа универсальных делегатов похожи на эффекты ковариации и контравариации в обычной привязке делегатов (см. вариативность в делегатах (C#) и вариативность в делегатах (Visual Basic). Однако дисперсия в привязке делегата работает со всеми типами делегатов, а не только с универсальными типами делегатов, имеющими параметры типа вариантов. Кроме того, дисперсия в привязке делегата позволяет привязать метод к любому делегату, который имеет более строгие типы параметров и менее строгий тип возвращаемого значения, в то время как назначение универсальных делегатов работает только в том случае, если оба типа делегата создаются из одного определения универсального типа.
В следующем примере показаны комбинированные эффекты вариативности в привязке делегатов и изменчивости в параметрах обобщенного типа. В примере определяется иерархия типов, которая включает три типа, от наименее производных (Type1
) до большинства производных (Type3
). Вариативность в обычной привязке делегата используется для привязки метода, у которого тип параметра Type1
и возвращаемый тип Type3
, к обобщенному делегату, параметр которого имеет тип Type2
, а возвращаемый тип — Type2
. Затем результирующий универсальный делегат назначается другой переменной, универсальный тип делегата которого имеет параметр типа Type3
и возвращаемый тип Type1
, используя ковариацию и контравариантность параметров универсального типа. Второе назначение требует создания переменной и типа делегата из одного определения универсального типа в данном случае Func<T,TResult>.
using System;
public class Type1 {}
public class Type2 : Type1 {}
public class Type3 : Type2 {}
public class Program
{
public static Type3 MyMethod(Type1 t)
{
return t as Type3 ?? new Type3();
}
static void Main()
{
Func<Type2, Type2> f1 = MyMethod;
// Covariant return type and contravariant parameter type.
Func<Type3, Type1> f2 = f1;
Type1 t1 = f2(new Type3());
}
}
Public Class Type1
End Class
Public Class Type2
Inherits Type1
End Class
Public Class Type3
Inherits Type2
End Class
Public Class Program
Public Shared Function MyMethod(ByVal t As Type1) As Type3
Return If(TypeOf t Is Type3, t, New Type3())
End Function
Shared Sub Main()
Dim f1 As Func(Of Type2, Type2) = AddressOf MyMethod
' Covariant return type and contravariant parameter type.
Dim f2 As Func(Of Type3, Type1) = f1
Dim t1 As Type1 = f2(New Type3())
End Sub
End Class
Определение вариантных универсальных интерфейсов и делегатов
В Visual Basic и C# есть ключевые слова, позволяющие пометить параметры универсального типа интерфейсов и делегатов как ковариантные или контравариантные.
Параметр ковариантного типа помечается out
ключевым словом (Out
ключевым словом в Visual Basic). Параметр ковариантного типа можно использовать в качестве возвращаемого значения метода, который принадлежит интерфейсу, или в качестве возвращаемого типа делегата. Параметр ковариантного типа нельзя использовать в качестве ограничения универсального типа для методов интерфейса.
Замечание
Если метод интерфейса имеет параметр, который является универсальным типом делегата, параметр ковариантного типа типа интерфейса можно использовать для указания параметра контравариантного типа типа делегата.
Параметр контравариантного типа помечается in
ключевым словом (In
ключевым словом в Visual Basic). Параметр контравариантного типа можно использовать в качестве типа параметра метода, который принадлежит интерфейсу, или в качестве типа параметра делегата. Параметр контравариантного типа можно использовать в качестве ограничения универсального типа для метода интерфейса.
Только типы интерфейсов и типы делегатов могут иметь параметры типа вариантов. Тип интерфейса или делегата может иметь как ковариантные, так и контравариантные параметры типа.
Visual Basic и C# не позволяют нарушать правила использования параметров ковариантного и контрвариантного типа, а также добавлять ковариантные и контравариантные заметки к параметрам типов, отличных от интерфейсов и делегатов.
Сведения и пример кода см. в разделе "Вариативность" в универсальных интерфейсах (C#) и вариативности в универсальных интерфейсах (Visual Basic).
Список типов
Следующие типы интерфейса и делегата имеют ковариантные и/или контравариантные параметры типа.
Тип | Ковариантные параметры типа | Контравариантные параметры типа |
---|---|---|
Action<T> до Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16> | Да | |
Comparison<T> | Да | |
Converter<TInput,TOutput> | Да | Да |
Func<TResult> | Да | |
Func<T,TResult> до Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult> | Да | Да |
IComparable<T> | Да | |
Predicate<T> | Да | |
IComparer<T> | Да | |
IEnumerable<T> | Да | |
IEnumerator<T> | Да | |
IEqualityComparer<T> | Да | |
IGrouping<TKey,TElement> | Да | |
IOrderedEnumerable<TElement> | Да | |
IOrderedQueryable<T> | Да | |
IQueryable<T> | Да |