Megosztás a következőn keresztül:


Kovariancia és kontravariancia a generikumokban

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.

    Hozzárendelhet egy IEnumerable<Derived> példányt egy IEnumerable<Base> típusú változóhoz.

  • Contravariance

    Lehetővé teszi az eredetileg megadottnál általánosabb (kevésbé származtatott) típus használatát.

    Hozzárendelhet egy Action<Base> példányt egy Action<Derived> típusú 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 egy List<Base> példányt egy List<Derived> típusú változóhoz, vagy fordítva sem.

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álja IEnumerable<Derived>. A kovariant típusú paraméter elvégzi a többit.

A kontravariancia viszont ellentmondásosnak 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ítódik és fut. 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 T delegált típusparamétere Action<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 Derivedkell 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 kovariancia és az ellenvariancia együttes elnevezése variancia. A kovariant vagy a contravariant jelöléssel nem rendelkező általános típusparamétert invariantnak nevezzük. A közös 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 ha adott két delegátum, Action<Derived> és Action<Base> típusú (Action(Of Derived) és Action(Of Base) a Visual Basicben), nem kombinálhatja a második delegátumot az elsővel, bár az eredmény típusbiztos 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ódus deklarálhat a felülbírált metódusnál származtatottabb visszatérési típust, és a felülbíráló, írásvédett tulajdonság is deklarálhat származtatottabb típust.

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 egy statikus metódus neve PrintBases, amely egy IEnumerable<Base> (IEnumerable(Of Base) a Visual Basicben) paramétert vesz át, és kinyomtatja az elemeket. Derived örököl Base-től. A példa létrehoz egy üres List<Derived> objektumot (List(Of Derived) a Visual Basicben), és bemutatja, hogy ez a típus átalakítás nélkül átadható a PrintBases-nek, és hozzárendelhető egy IEnumerable<Base> típusú változóhoz. 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 ShapeAreaComparer osztályt is definiál, amely megvalósítja a IComparer<Shape>-t (IComparer(Of Shape) a Visual Basicben). A IComparer<T>.Compare metódus megvalósítása a Area tulajdonság értékén alapul, így ShapeAreaComparer a terület szerinti rendezésére használható Shape objektumok.

Az Circle osztály örökli a Shape és felülbírálja a Area metódust. A példa egy SortedSet<T> hoz létre Circle objektumokból, egy olyan konstruktor használatával, amely IComparer<Circle>-t vesz igénybe (IComparer(Of Circle) a Visual Basicben). A példa azonban ahelyett, hogy egy IComparer<Circle>-t adna át, egy ShapeAreaComparer objektumot ad át, amely megvalósítja IComparer<Shape>-t. 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>.CompareIComparer(Of Shape).Compare objektum metódusát (ShapeAreaComparermetó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 kontravariancia lehetővé teszi ShapeAreaComparer számára, hogy bármilyen típusú gyűjteményt, valamint egy vegyes típusokból álló gyűjteményt is rendezzen, melyek a Shape típusból származnak.

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 delegátumok, például Func<T,TResult>, a kovariáns visszatérési típusokkal és a kontravariáns paramétertípusokkal rendelkeznek. Az Action általános delegátusok, például Action<T1,T2>, kontravariáns paramétertípusúak. Ez azt jelenti, hogy a delegáltak olyan változókhoz rendelhetők, amelyek több származtatott paramétertípussal rendelkeznek, és (az Func általános delegáltak esetében) kevésbé származtatott visszatérési típusokkal.

Megjegyzé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ó).

Ezt az alábbi kód szemlélteti. Az első kódrészlet definiál egy Base nevű osztályt, egy Derived-t öröklő Base nevű osztályt, és egy másik osztályt static nevű Shared metódussal (MyMethod a Visual Basicben). A metódus egy Base példányt fogad, és egy Derived példányt ad vissza. (Ha az argumentum a példánya Derived, MyMethod azt adja vissza; ha az argumentum a példánya Base, MyMethod akkor a függvény egy új példányt Derived ad vissza.) Ebben a Main() példában a Visual Basicben Func<Base, Derived>, egy példányt Func(Of Base, Derived) hoz létre, amely MyMethod jelöli, és a változóban f1 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 delegáltak esetében

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 kontravariancia hatása a generikus delegáltak típusparamétereiben hasonló a kovariancia és a kontravariancia szokványos delegált kötésnél (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 azt a célt szolgálja, hogy egy Type1 paramétertípusú és Type3 visszatérési típusú metódust köt össze egy általános delegálttal, amelynek paramétertípusa Type2 és visszatérési típusa Type2. 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 Type1rendelkezik, 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 kovariáns típusparaméter a out kulcsszóval van megjelölve (Out kulcsszóval Visual Basicben). 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.

Megjegyzé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 kontravariáns típusparaméter a in kulcsszóval van megjelölve (In kulcsszó Visual Basic-ben). 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> és Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16> Igen
Comparison<T> Igen
Converter<TInput,TOutput> Igen Igen
Func<TResult> Igen
Func<T,TResult> és Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,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

Lásd még