Kovariancia és contravariance a genericsban
A kovariancia és az ellenvariancia olyan kifejezések, amelyek az eredetileg megadottnál származtatottabb (pontosabb) vagy kevésbé származtatott (kevésbé specifikus) típus használatára utalnak. Az általános típusparaméterek támogatják a kovarianciát és a contravariance-t, hogy nagyobb rugalmasságot biztosítsanak az általános típusok hozzárendelésében és használatában.
Amikor típusrendszerre hivatkozik, a kovariancia, a contravariance és az invariance a következő definíciókkal rendelkezik. A példák egy elnevezett Base
alaposztályt és egy származtatott osztályt feltételeznek Derived
.
Covariance
Lehetővé teszi, hogy az eredetileg megadottnál származtatottabb típust használjon.
Egy példányt
IEnumerable<Derived>
hozzárendelhet egy típusúIEnumerable<Base>
változóhoz.Contravariance
Lehetővé teszi az eredetileg megadottnál általánosabb (kevésbé származtatott) típus használatát.
Egy példányt
Action<Base>
hozzárendelhet egy típusúAction<Derived>
változóhoz.Invariance
Azt jelenti, hogy csak az eredetileg megadott típust használhatja. Az invariáns általános típusparaméter nem covariant vagy contravariant.
Nem rendelhet példányt
List<Base>
egy típusúList<Derived>
változóhoz, vagy fordítva.
A covariant típusú paraméterek lehetővé teszik olyan hozzárendelések elvégzését, amelyek a szokásos polimorfizmushoz hasonlóan néznek ki, ahogyan az az alábbi kódban is látható.
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
Az List<T> osztály implementálja az IEnumerable<T> interfészt, így List<Derived>
(List(Of Derived)
a Visual Basicben) implementál.IEnumerable<Derived>
A kovariant típusú paraméter elvégzi a többit.
A contravariance viszont ellenintuitívnak tűnik. Az alábbi példa létrehoz egy típusú Action<Base>
delegáltat (Action(Of Base)
a Visual Basicben), majd hozzárendeli a delegáltat egy típusváltozóhoz 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())
Ez visszafelé néz ki, de ez egy olyan típusbiztos kód, amely lefordítja és futtatja. A lambda kifejezés megegyezik a hozzárendelt delegálttal, ezért egy olyan metódust határoz meg, amely egy típusú Base
paramétert használ, és amelynek nincs visszatérési értéke. Az eredményül kapott delegált hozzárendelhető egy típusváltozóhozAction<Derived>
, mert a Action<T> delegált típusparamétere T
contravariant. A kód típusbiztos, mert T
paramétertípust ad meg. Ha a típusmegbízott Action<Base>
úgy van meghívva, mintha típusmegbízott Action<Derived>
lenne, az argumentumának típusnak Derived
kell lennie . Ez az argumentum mindig biztonságosan továbbítható a mögöttes metódusnak, mert a metódus paramétere típus.Base
Általában egy kovariant típusú paraméter használható delegált visszatérési típusaként, a contravariant típusú paraméterek pedig paramétertípusokként. Egy interfész esetében a kovariant típusú paraméterek használhatók az interfész metódusainak visszatérési típusaiként, a contravariant típusú paraméterek pedig az interfész metódusainak paramétertípusaiként.
A kovarianciát és az ellenvarianciát együttesen varianciának nevezzük. A kovariant vagy a contravariant jelöléssel nem rendelkező általános típusparamétert invariantnak nevezzük. A gyakori nyelvi futtatókörnyezet varianciájával kapcsolatos tények rövid összefoglalása:
A variánstípus-paraméterek az általános felületre és az általános delegálási típusra korlátozódnak.
Az általános illesztő- vagy általános delegálási típus kovarianit és contravariant típusú paraméterekkel is rendelkezhet.
A variancia csak referenciatípusokra vonatkozik; ha értéktípust ad meg egy variánstípus-paraméterhez, az adott típusparaméter invariáns az eredményként létrehozott típushoz.
A variancia nem vonatkozik a delegált kombinációra. Ez azt jelenti, hogy két típusú
Action<Derived>
Action<Base>
delegáltat és (Action(Of Derived)
ésAction(Of Base)
a Visual Basicben) nem kombinálhatja a második delegáltat az elsővel, bár az eredmény biztonságos típus lenne. A variancia lehetővé teszi, hogy a második delegált egy típusúAction<Derived>
változóhoz legyen rendelve, de a meghatalmazottak csak akkor egyesíthetők, ha a típusok pontosan egyeznek.A C# 9-től kezdődően a kovátriai visszatérési típusok támogatottak. A felülbíráló metódusok a felülbírált metódussal származtatottabb visszatérési típust deklarálhatnak, a felülbíráló, írásvédett tulajdonság pedig származtatottabb típust deklarálhat.
Kovariant típusú paraméterekkel rendelkező általános interfészek
Számos általános felület kovariant típusú paraméterekkel rendelkezik, IEnumerable<T>például , IEnumerator<T>, IQueryable<T>és IGrouping<TKey,TElement>. Ezeknek az interfészeknek az összes típusparamétere kovariant, így a típusparaméterek csak a tagok visszatérési típusaihoz használhatók.
Az alábbi példa a kovariant típusú paramétereket mutatja be. A példa két típust határoz meg: Base
van egy statikus metódus nevePrintBases
, amely egy (IEnumerable(Of Base)
a Visual Basicben) részt vesz IEnumerable<Base>
, és kinyomtatja az elemeket. Derived
örökli a .-tól Base
. A példa létrehoz egy üreset List<Derived>
(List(Of Derived)
a Visual Basicben), és bemutatja, hogy ez a típus öntés nélkül adható át PrintBases
és rendelhető hozzá egy változóhoz IEnumerable<Base>
. List<T> implementálja IEnumerable<T>, amely egyetlen kovariant típusú paraméterrel rendelkezik. A kovariant típusú paraméter az oka annak, hogy egy példány IEnumerable<Derived>
használható helyett 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
Általános felületek contravariant típusú paraméterekkel
Számos általános adapter rendelkezik contravariant típusú paraméterekkel; például: IComparer<T>, IComparable<T>és IEqualityComparer<T>. Ezek az interfészek csak contravariant típusú paraméterekkel rendelkeznek, így a típusparaméterek csak paramétertípusként használhatók az interfészek tagjaiban.
Az alábbi példa a contravariant típusú paramétereket mutatja be. A példa egy absztrakt (MustInherit
Visual Basic) Shape
osztályt határoz meg egy Area
tulajdonsággal. A példa egy (Visual Basicben) implementálhatóIComparer(Of Shape)
IComparer<Shape>
osztályt ShapeAreaComparer
is definiál. A metódus megvalósítása a IComparer<T>.Compare tulajdonság értékén Area
alapul, így ShapeAreaComparer
az objektumok terület szerinti rendezésére Shape
használható.
Az Circle
osztály örökli Shape
és felülbírálja a parancsot Area
. A példa objektumokat hoz létre SortedSet<T>Circle
egy olyan konstruktor használatával, amely (IComparer(Of Circle)
a Visual Basicben) egy objektumot IComparer<Circle>
vesz igénybe. A példa ShapeAreaComparer
azonban ahelyett, hogy átad egy IComparer<Circle>
objektumot, amely megvalósítja IComparer<Shape>
a műveletet. A példa átadhat egy kevésbé származtatott (Shape
) típusú összehasonlítót, amikor a kód egy származtatottabb (Circle
) típusú összehasonlítót hív meg, mivel az IComparer<T> általános felület típusparamétere contravariant.
Amikor új Circle
objektumot ad hozzá az SortedSet<Circle>
objektumhoz, a rendszer minden alkalommal meghívja az IComparer<Shape>.Compare
ShapeAreaComparer
objektum metódusát (IComparer(Of Shape).Compare
metódusát a Visual Basicben), amikor az új elemet egy meglévő elemhez hasonlítják. A metódus (Shape
) paramétertípusa kevésbé származtatott, mint az átadott (Circle
) típus, így a hívás típusa biztonságos. A contravariance lehetővé tesziShapeAreaComparer
, hogy bármilyen típusú gyűjteményt, valamint vegyes típusok gyűjteményét rendezze, amelyek származnak.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
Általános meghatalmazottak variánstípus-paraméterekkel
Az Func
általános meghatalmazottak, például Func<T,TResult>a kovariant visszatérési típusok és a contravariant paramétertípusok. Az Action
általános meghatalmazottak, például Action<T1,T2>a contravariant paramétertípusokkal rendelkeznek. Ez azt jelenti, hogy a delegáltak olyan változókhoz rendelhetők, amelyek több származtatott paramétertípussal rendelkeznek, és (általános Func
meghatalmazottak esetén) kevésbé származtatott visszatérési típusok.
Feljegyzés
Az általános meghatalmazottak utolsó általános típusparamétere Func
határozza meg a delegált aláírás visszatérési értékének típusát. Ez covariant (out
kulcsszó), míg a többi általános típusparaméter a contravariant (in
kulcsszó).
Az alábbi kód ezt mutatja be. Az első kódrészlet definiál egy osztályt Base
, egy öröklő Derived
Base
osztályt, egy másik osztályt pedig egy static
metódussal (Shared
a Visual Basicben) elnevezve MyMethod
. A metódus egy példányt Base
vesz fel, és egy példányt Derived
ad vissza. (Ha az argumentum a példányaDerived
, MyMethod
azt adja vissza; ha az argumentum a példányaBase
, MyMethod
akkor a függvény egy új példányt Derived
ad vissza.) Ebben Main()
a példában a (Visual Basicben) egy példányt Func<Base, Derived>
Func(Of Base, Derived)
hoz létre, amely a változót f1
jelöli MyMethod
és tárolja.
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
A második kódrészlet azt mutatja, hogy a delegált hozzárendelhető egy típusú Func<Base, Base>
változóhoz (Func(Of Base, Base)
a Visual Basicben), mert a visszatérési típus kovariant.
// 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())
A harmadik kódrészlet azt mutatja, hogy a delegált hozzárendelhető egy típusú Func<Derived, Derived>
változóhoz (Func(Of Derived, Derived)
a Visual Basicben), mert a paraméter típusa contravariant.
// 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())
Az utolsó kódrészlet azt mutatja, hogy a delegált hozzárendelhető egy típusváltozóhoz Func<Derived, Base>
(Func(Of Derived, Base)
a Visual Basicben), kombinálva a contravariant paramétertípus és a kovariant visszatérési típus hatásait.
// 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())
Variancia nem általános meghatalmazottakban
Az előző kódban az aláírás MyMethod
pontosan megegyezik a létrehozott általános meghatalmazott aláírásával: Func<Base, Derived>
(Func(Of Base, Derived)
a Visual Basicben). A példa azt mutatja, hogy ez az általános delegált olyan változókban vagy metódusparaméterekben tárolható, amelyek több származtatott paramétertípussal és kevésbé származtatott visszatérési típussal rendelkeznek, feltéve, hogy az összes delegálttípus az általános delegálási típusból Func<T,TResult>lett létrehozva.
Ez egy fontos pont. A kovariancia és a contravariance hatása az általános meghatalmazottak típusparamétereiben hasonló a kovariancia és a contravariance normál delegálási kötésben (lásd : Variance in Delegates (C#) és Variance in Delegates (Visual Basic)). A delegált kötés varianciája azonban minden delegálttípussal működik, nem csak az általános delegálási típusok esetében, amelyek variánstípus-paraméterekkel rendelkeznek. Emellett a delegáltkötések varianciája lehetővé teszi, hogy a metódusok minden olyan delegálthoz kötődjenek, amely szigorúbb paramétertípusokkal és kevésbé korlátozó visszatérési típussal rendelkezik, míg az általános meghatalmazottak hozzárendelése csak akkor működik, ha mindkét delegálttípus ugyanabból az általános típusdefinícióból lett létrehozva.
Az alábbi példa a variancia együttes hatását mutatja be a delegált kötésben és a varianciában az általános típusparaméterekben. A példa egy típushierarchiát határoz meg, amely három típust tartalmaz, a legkevésbé származtatott (Type1
) típustól a legtöbb származtatottig (Type3
). A szokásos delegált kötés varianciája egy metódus paramétertípussal Type1
és egy általános delegálthoz való visszatérési Type3
típussal történő kötésére szolgál, amelynek paramétertípusa Type2
és visszatérési Type2
típusa. Az eredményül kapott általános delegált ezután egy másik változóhoz lesz hozzárendelve, amelynek általános delegálási típusa típusparaméterrel Type3
és visszatérési típussal Type1
rendelkezik, az általános típusú paraméterek kovarianciájának és ellentravarianciájának használatával. A második hozzárendeléshez a változótípust és a delegálttípust is ugyanabból az általános típusdefinícióból kell létrehozni, Func<T,TResult>ebben az esetben.
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
Variáns általános interfészek és delegáltak definiálása
A Visual Basic és a C# kulcsszavak lehetővé teszik az interfészek és meghatalmazottak általános típusparamétereinek kovariantikus vagy contravariantként való megjelölését.
A covariant típusú paraméterek a kulcsszóval (Out
a Visual Basic kulcsszójávalout
) vannak megjelölve. A kovariáns típusú paramétert használhatja egy felülethez tartozó metódus visszatérési értékeként, vagy meghatalmazott visszatérési típusaként. A kovarians típusparaméter nem használható általános típuskényszerként az interfészmetelyekhez.
Feljegyzés
Ha egy interfész valamely metódusa egy általános delegált típusú paraméterrel rendelkezik, a felülettípus kovariáns típusú paramétere használható a delegált típus contravariant típusú paraméterének megadására.
A contravariant típusú paraméter a kulcsszóval (In
a in
Visual Basic kulcsszójával) van megjelölve. Használhatja a contravariant típusú paramétert egy felülethez tartozó metódus paraméterének típusaként, vagy egy meghatalmazott paraméterének típusaként. A contravariant típusú paramétert általános típuskényszerként használhatja egy interfészmetódushoz.
Csak a felülettípusok és a delegált típusok rendelkezhetnek változattípus-paraméterekkel. Az interfész- vagy delegálástípusnak lehetnek kovariant és contravariant típusú paraméterei is.
A Visual Basic és a C# nem teszi lehetővé, hogy megsértse a kovariáns és a contravariant típusú paraméterek használatára vonatkozó szabályokat, vagy hogy kovariancia- és ellenvariancia-megjegyzéseket adjon hozzá az interfészeken és delegáltakon kívüli típusok típusparamétereihez.
További információ és példakód: Variance in Generic Interfaces (C#) and Variance in Generic Interfaces (Visual Basic).
Típuslista
Az alábbi interfész- és delegálástípusok kovariant és/vagy contravariant típusú paraméterekkel rendelkeznek.
Típus | Covariant-típusparaméterek | Contravariant típusú paraméterek |
---|---|---|
Action<T> / |
Igen | |
Comparison<T> | Igen | |
Converter<TInput,TOutput> | Igen | Igen |
Func<TResult> | Igen | |
Func<T,TResult> / |
Igen | Igen |
IComparable<T> | Igen | |
Predicate<T> | Igen | |
IComparer<T> | Igen | |
IEnumerable<T> | Igen | |
IEnumerator<T> | Igen | |
IEqualityComparer<T> | Igen | |
IGrouping<TKey,TElement> | Igen | |
IOrderedEnumerable<TElement> | Igen | |
IOrderedQueryable<T> | Igen | |
IQueryable<T> | Igen |