Sdílet prostřednictvím


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é typu IEnumerable<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é typu Action<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 TAction<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> a Action<Base> (Action(Of Derived) a Action(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 typu Action<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 (Sharedv jazyce Visual Basic) pojmenovanou staticMyMethod. 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 MyMethoda 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

Viz také