Kovariance a kontravariance u obecných typů
Kovariance a kontravariance jsou termíny, které odkazují na schopnost používat více odvozeného typu (konkrétnější) nebo menší odvozený typ (méně specifický) než původně zadaný. Parametry obecného typu podporují kovarianci a kontravarianci za účelem zvýšení flexibility při přiřazování a používání obecných typů.
Pokud odkazujete na systém typů, kovariance, kontravariance a invariance, mají následující definice. Příklady předpokládají základní třídu pojmenovanou Base
a odvozenou třídu s názvem Derived
.
Covariance
Umožňuje použít odvozenější typ než původně zadaný.
Můžete přiřadit instanci
IEnumerable<Derived>
proměnné typuIEnumerable<Base>
.Contravariance
Umožňuje používat obecnější (méně odvozený) typ, než byl původně zadán.
Můžete přiřadit instanci
Action<Base>
proměnné typuAction<Derived>
.Invariance
To znamená, že můžete použít pouze původně zadaný typ. Parametr invariantního obecného typu není kovariantní ani kontravariant.
Instanci proměnné typu
List<Derived>
nebo naopak nelze přiřaditList<Base>
.
Parametry kovariantního typu umožňují vytvářet přiřazení, která vypadají podobně jako běžná polymorfismus, jak je znázorněno v následujícím kódu.
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
Třída List<T> implementuje IEnumerable<T> rozhraní, takže List<Derived>
(List(Of Derived)
v jazyce Visual Basic) implementuje IEnumerable<Derived>
. Parametr kovariantního typu dokončí zbývající úkoly.
Kontravariance se naopak zdá být neintuitivní. Následující příklad vytvoří delegáta typu Action<Base>
(Action(Of Base)
v jazyce Visual Basic) a pak přiřadí tento delegát proměnné typu 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())
Zdá se, že se jedná o zpětnou akci, jde však o typově bezpečný kód, který se zkompiluje a spustí. Výraz lambda odpovídá delegátovi, kterému je přiřazen, takže definuje metodu, která přebírá jeden parametr typu Base
a nemá žádnou návratovou hodnotu. Výsledný delegát lze přiřadit proměnné typu Action<Derived>
, protože parametr T
Action<T> typu delegáta je kontravariantní. Kód je typově bezpečný, protože T
určuje typ parametru. Při vyvolání delegáta typu Action<Base>
, jako by byl delegát typu Action<Derived>
, jeho argument musí být typu Derived
. Tento argument lze vždy bezpečně předat základní metodě, protože parametr metody je typu Base
.
Obecně lze parametr konvariantního typu použít jako návratový typ delegátu a parametry kontravariantního typu mohou být použity jako typy parametrů. V případě konkrétního rozhraní mohou být parametry kovariantního typu použity jako návratové typy metod rozhraní a parametry kontravariantního typu mohou být použity jako typy parametrů metod rozhraní.
Kovariance a kontravariance se souhrnně označují jako rozptyl. Obecný parametr typu, který není označen kovariantní nebo kontravariant, se označuje jako invariantní. Stručný souhrn faktů o varianci v modulu CLR (Common Language Runtime):
Parametry variantního typu jsou omezeny na obecné rozhraní a obecné typy delegátů.
Obecná rozhraní nebo obecné typy delegátů mohou mít parametry kovariantního i kontravariantního typu.
Variance platí pouze pro odkazované typy. Pokud zadáte typ hodnoty pro parametr variantního typu, je tento parametr typu pro výsledný konstruovaný typ invariantní.
Variance se nevztahuje na kombinaci delegátů. To znamená, že vzhledem k tomu, že dva delegáti typů
Action<Derived>
aAction<Base>
(Action(Of Derived)
aAction(Of Base)
v jazyce Visual Basic), nemůžete druhý delegát kombinovat s prvním, i když by výsledek byl typ bezpečný. Rozptyl umožňuje druhému delegátovi přiřadit proměnnou typuAction<Derived>
, ale delegáti mohou kombinovat pouze v případě, že jejich typy přesně odpovídají.Počínaje jazykem C# 9 se podporují kovariantní návratové typy. Metoda přepsání může deklarovat odvozenější návratový typ, který přepíše, a přepsání, jen pro čtení vlastnost může deklarovat více odvozený typ.
Obecná rozhraní s kovariantnými parametry typu
Několik obecných rozhraní má kovariantní parametry typu, IEnumerable<T>například , IEnumerator<T>, , IQueryable<T>a IGrouping<TKey,TElement>. Všechny parametry typu těchto rozhraní jsou kovariantní. Parametry typu se tedy používají pouze pro návratové typy členů.
Následující příklad znázorňuje parametry kovariantního typu. Příklad definuje dva typy: Base
má statickou metodu s názvem PrintBases
, která přebírá IEnumerable<Base>
(IEnumerable(Of Base)
v jazyce Visual Basic) a vytiskne prvky. Derived
dědí z Base
. Příklad vytvoří prázdnou List<Derived>
(List(Of Derived)
v jazyce Visual Basic) a ukazuje, že tento typ lze předat PrintBases
a přiřadit proměnné typu IEnumerable<Base>
bez přetypování. List<T> implementuje IEnumerable<T>, který má jeden kovariantní typ parametr. Parametr kovariantního typu je důvodem, proč lze místo IEnumerable<Derived>
instance použít 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
Obecná rozhraní s parametry kontravariantního typu
Několik obecných rozhraní má kontravariantní parametry typu; například: IComparer<T>, IComparable<T>a IEqualityComparer<T>. Tato rozhraní mají pouze parametry kontravariantního typu, takže parametry typu slouží pouze jako typy parametrů u členů rozhraní.
Následující příklad znázorňuje parametry kontravariantního typu. Příklad definuje abstraktní třídu (MustInherit
v jazyce Visual Basic) Shape
s Area
vlastností. Příklad také definuje ShapeAreaComparer
třídu, která implementuje IComparer<Shape>
(IComparer(Of Shape)
v jazyce Visual Basic). Implementace IComparer<T>.Compare metody je založena na hodnotě Area
vlastnosti, takže ShapeAreaComparer
lze použít k řazení Shape
objektů podle oblasti.
Třída Circle
dědí Shape
a přepisuje Area
. Příklad vytvoří objekty SortedSet<T>Circle
pomocí konstruktoru, který přebírá ( IComparer<Circle>
IComparer(Of Circle)
v jazyce Visual Basic). Místo předání příkladu IComparer<Circle>
však předá ShapeAreaComparer
objekt, který implementuje IComparer<Shape>
. Příklad může předat porovnávač méně odvozeného typu (Shape
), když kód volá porovnávač více odvozeného typu (Circle
), protože parametr IComparer<T> typu obecného rozhraní je kontravariantní.
Když se do objektu přidá nový Circle
objekt , IComparer<Shape>.Compare
metoda (IComparer(Of Shape).Compare
metoda v jazyce Visual Basic) ShapeAreaComparer
objektu je volána pokaždé, když se nový prvek porovná s existujícím elementem.SortedSet<Circle>
Typ parametru metody (Shape
) je menší než typ, který se předává (Circle
), takže volání je bezpečné. Kontravariance umožňuje ShapeAreaComparer
řadit kolekci libovolného jednoho typu, stejně jako smíšenou kolekci typů, která je odvozena od 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
Obecné delegáty s parametry variantního typu
Obecné Func
delegáty, například Func<T,TResult>, mají kovariantní návratové typy a kontravariantní typy parametrů. Obecné Action
delegáty, například Action<T1,T2>, mají kontravariantní typy parametrů. To znamená, že delegáti mohou být přiřazeni proměnným, které mají více odvozených typů parametrů a (v případě Func
obecných delegátů) méně odvozených návratových typů.
Poznámka:
Poslední parametr Func
obecného typu obecných delegátů určuje typ návratové hodnoty v podpisu delegáta. Je kovariantní (out
klíčové slovo), zatímco ostatní parametry obecného typu jsou kontravariantní (in
klíčové slovo).
Následující kód to ilustruje. První část kódu definuje třídu pojmenovanou Base
, třídu, Derived
která dědí Base
, a další třídu s metodou (Shared
v jazyce Visual Basic) pojmenovanou static
MyMethod
. Metoda přebírá instanci Base
a vrací instanci Derived
. (Pokud je argument instancí Derived
, MyMethod
vrátí jej; pokud je argumentem instance Base
, MyMethod
vrátí novou instanci Derived
.) V Main()
příkladu vytvoří instanci Func<Base, Derived>
(Func(Of Base, Derived)
v jazyce Visual Basic), která představuje MyMethod
a uloží ji do proměnné 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
Druhá část kódu ukazuje, že delegát může být přiřazen k proměnné typu Func<Base, Base>
(Func(Of Base, Base)
v jazyce Visual Basic), protože návratový typ je kovariantní.
// 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())
Třetí část kódu ukazuje, že delegát lze přiřadit proměnné typu Func<Derived, Derived>
(Func(Of Derived, Derived)
v jazyce Visual Basic), protože typ parametru je kontravariantní.
// 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())
Poslední část kódu ukazuje, že delegát může být přiřazen k proměnné typu Func<Derived, Base>
(Func(Of Derived, Base)
v jazyce Visual Basic), kombinování účinků kontravariantního typu parametru a kovariantního návratového typu.
// 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())
Rozptyl u jiných než obecných delegátů
V předchozím kódu podpis MyMethod
přesně odpovídá podpisu vytvořeného obecného delegáta: Func<Base, Derived>
(Func(Of Base, Derived)
v jazyce Visual Basic). Příklad ukazuje, že tento obecný delegát může být uložen v proměnných nebo parametrech metody, které mají více odvozených typů parametrů a méně odvozených návratových typů, pokud jsou všechny typy delegátů sestaveny z obecného typu Func<T,TResult>delegáta .
Jedná se o důležitý fakt. Účinky kovariance a kontravariance v parametrech typu obecných delegátů se podobají účinkům kovariance a kontravariance v běžné vazbě delegáta (viz Rozptyl v delegátech (C#) a Variance v delegátech (Visual Basic)). Variance ve vazbě delegátu však funguje se všemi typy delegátů, nejen s obecnými typy delegátu, které mají parametry variantního typu. Variance ve vazbách delegátů navíc umožňuje metodě vázat se na jakýkoli delegát, který má více omezující parametry typu a méně omezující návratový typ, zatímco přiřazení obecných delegátů funguje pouze v případě, že oba typy delegátů jsou konstruovány ze stejné definice obecného typu.
Následující příklad znázorňuje kombinované účinky variance ve vazbě delegátu a variance u parametrů obecného typu. Příklad definuje hierarchii typů, která obsahuje tři typy, od nejmenšího odvozeného (Type1
) po většinu odvozených (Type3
). Rozptyl v běžné vazbě delegáta se používá k vytvoření vazby metody s typem Type1
parametru a návratovým typem Type3
obecného delegáta s typem parametru Type2
a návratovým typem Type2
. Výsledný obecný delegát je pak přiřazen k jiné proměnné, jejíž obecný typ delegátu má parametr typu Type3
a návratový typ Type1
, pomocí kovariance a kontravariance obecných parametrů typu. Druhé přiřazení vyžaduje, aby byl v tomto případě Func<T,TResult>vytvořen typ proměnné i typ delegáta ze stejné definice obecného typu .
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
Definování variant obecných rozhraní a delegátů
Visual Basic a C# obsahují klíčová slova, která umožňují označit obecné parametry typu rozhraní a delegátů jako kovariantní nebo kontravariantní.
Parametr kovariantního typu je označen klíčovým slovem out
(Out
klíčové slovo v jazyce Visual Basic). Parametr kovariantního typu můžete použít jako návratovou hodnotu metody, která patří do rozhraní, nebo jako návratový typ delegátu. Typ kovariantního parametru nelze použít jako omezení obecného typu pro metody rozhraní.
Poznámka:
Pokud má metoda rozhraní parametr, který je typem obecného delegátu, může parametr kovariantního typu pro typ rozhraní být použit pro zadání parametru kontravariantního typu pro typ delegátu.
Parametr kontravariantního in
typu je označen klíčovým slovem (In
klíčové slovo v jazyce Visual Basic). Parametr kontravariantního typu můžete použít jako typ parametru metody, která patří do rozhraní, nebo jako typ parametru delegátu. Parametr kontravariantního typu lze použít jako omezení obecného typu pro metodu rozhraní.
Pouze typy rozhraní a typy delegátů mohou mít parametry variantního typu. Typy rozhraní nebo delegátů mohou mít parametry kovariantního i kontravariantního typu.
Jazyky Visual Basic a C# neumožňují porušení pravidel pro použití parametrů kovariantního a kontravariantního typu nebo přidání anotací kovariance a kontravariance parametrům typu pro jiné typy, než jsou typy rozhraní a delegátů.
Informace a příklad kódu naleznete v tématu Variance in Generic Interfaces (C#) and Variance in Generic Interfaces (Visual Basic).
Seznam typů
Následující typy rozhraní a delegátů mají kovariantní parametry a/nebo kontravariantního typu.
Typ | Parametry kovariantního typu | Parametry kontravariantního typu |
---|---|---|
Action<T> na Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16> | Ano | |
Comparison<T> | Ano | |
Converter<TInput,TOutput> | Ano | Ano |
Func<TResult> | Yes | |
Func<T,TResult> na Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult> | Ano | Ano |
IComparable<T> | Ano | |
Predicate<T> | Ano | |
IComparer<T> | Ano | |
IEnumerable<T> | Ano | |
IEnumerator<T> | Ano | |
IEqualityComparer<T> | Ano | |
IGrouping<TKey,TElement> | Ano | |
IOrderedEnumerable<TElement> | Ano | |
IOrderedQueryable<T> | Ano | |
IQueryable<T> | Yes |