Kovariance a kontravariance v obecných typech

Parametry kovariance a kontravariance obecných typů poskytují větší pružnost při přiřazení a použití obecných typů. Například parametry kovariance typu umožňují provést přiřazení, která vypadají jako běžný polymorfismus. Předpokládejme, že máte základní a odvozenou třídu, s názvy Base a Derived. Polymorfismus vám umožní přiřadit instanci třídy Derived proměnné typu Base. Protože parametr typu rozhraní IEnumerable<T> je kovarianční, můžete přiřadit instanci IEnumerable<Derived> (IEnumerable(Of Derived) v jazyce Visual Basic) proměnné typu IEnumerable<Base>, jak ukazuje následující kód.

Dim d As IEnumerable(Of Derived) = New List(Of Derived)
Dim b As IEnumerable(Of Base) = d
IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;

Třída List<T> implementuje rozhraní IEnumerable<T>, takže List<Derived> (List(Of Derived) v jazyce Visual Basic) implementuje IEnumerable<Derived>. Parametr kovariance typu provede zbývající.

Kovariance se zdá velmi přírozená, protože vypadá jako polymorfismus. Kontravariance se na druhou stranu zdá neintuitivní. Následující příklad vytvoří delegáta typu Action<Base> (Action(Of Base) v jazyce Visual Basic) a poté tohoto delegáta přiřadí k proměnné typu Action<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())
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());

To vypadá špatně, ale jde o kód zajišťující bezpečnost typů, který se zkompiluje a spustí. Lambda výraz odpovídá delegátovi jemuž je přiřazen, takže definuje metodu, která přijímá jeden parametr typu Base a která nemá žádnou návratovou hodnotu. Výsledný delegát může být přiřazen proměnné typu Action<Derived>, protože typový parametr T delegáta Action<T> je kontravariantní. Kód je typově bezpečný, protože T určuje typ parametru. Když je vyvolán delegát typu Action<Base> jako by šlo o delegáta typu Action<Derived>, příslušný argument musí být typu Derived. Tento argument lze vždy předat základní metodě bezpečně, protože parametr metody je typu Base.

Obecně lze použít kovariantní parametr typu jako návratový typ delegáta a kontravariantní parametry typu mohou být použity jako typy parametrů. U rozhraní mohou být kovariantní parametry typu použity jako návratové typy metod rozhraní a kontravariantní parametry typu mohou být použity jako typy parametrů metod rozhraní.

Kovariance a kontravariance jsou souhrnně označovány jako variance. Parametr obecného typu, který není označen jako kovariantní nebo kontravariantní se nazývá Invariantní. Stručný souhrn skutečností o varianci v modulu CLR (Common Language Runtime):

  • V .NET Framework verze 4 jsou variantní parametry typu omezeny na obecné rozhraní a delegáty obecného typy.

  • Obecná rozhraní nebo typy obecných delegátů mohou mít kovariantní i kontravariantní parametry typu.

  • Variance platí pouze pro odkazované typ; pokud zadáte hodnotový typ pro variantní parametr typu, parametr typu je pro výsledný konstruovaný typ invariantní.

  • Variance se nevztahuje na kombinaci delegátů. To znamená, že u daných dvou delegátů typů Action<Derived> a Action<Base> (Action(Of Derived) a Action(Of Base) v jazyce Visual Basic) nelze seskupovat druhého delegáta s prvním, ačkoli by byl výsledek typově bezpečný. Variance umožňuje přiřazení druhého delegáta proměnné typu Action<Derived>, ale delegáti mohou být seskupování pouze v případě, že jejich typy souhlasí přesně.

Následující pododdíly popisují kovariantní a kontravariantní parametry typu podrobně:

  • Obecná rozhraní s kovariantními parametry typu

  • Obecná rozhraní s kontravariantními obecnými parametry typu

  • Obecní delegáti s variantními parametry typu

  • Definování variantních obecných rozhraní a delegátů

  • Seznam variantních obecných typů rozhraní a delegátů

Obecná rozhraní s kovariantními parametry typu

Počínaje .NET Framework 4 několik obecných rozhraní má kovariantní parametry typu; například: IEnumerable<T>IEnumerator<T>IQueryable<T>, and IGrouping<TKey, TElement>. Všechny parametry typu tato rozhraní jsou tedy parametry typu se používají pouze pro návratové typy členů. covariant, 

Následující příklad ukazuje kovariantní parametry typu. V příkladu jsou definovány dva typy: Base má statickou metodu s názvem PrintBases, která přijímá IEnumerable<Base> (IEnumerable(Of Base) v jazyce Visual Basic) a vytiskne prvky. Derived dědí z Base. V příkladu je vytvořen prázdný List<Derived> (List(Of Derived) v jazyce Visual Basic) a demonstruje, ž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á jediný kovariantní parametr typu. Kovariantní parametr typu je důvod, proč lze instanci IEnumerable<Derived> použít namísto IEnumerable<Base>.

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
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;
    }
}

Zpět na začátek

Obecná rozhraní s kontravariantními obecnými parametry typu

Počínaje .NET Framework 4 několik obecných rozhraní má kontravariantní parametry typu; například: IComparer<T>, IComparable<T> a IEqualityComparer<T>. Tato rozhraní mají pouze kontravariantní parametry typu, takže parametry typu slouží pouze jako typy parametrů ve členech rozhraní.

Následující příklad ukazuje kontravariantní parametry typu. Příklad definuje abstrakt (MustInherit v jazyce Visual Basic) Shape třídy s Area vlastnost. Příklad definuje také ShapeAreaComparer třídy, která implementuje rozhraní IComparer<Shape> ()IComparer(Of Shape) v jazyce Visual Basic). Provádění IComparer<T>.Compare Metoda je založena na hodnotě Area vlastnost, tak ShapeAreaComparer lze použít k řazení Shape objekty podle oblasti.

Circle Třída dědí Shape a přepíše Area. Příklad vytvoří SortedSet<T> z Circle objektů pomocí konstruktoru, která přebírá IComparer<Circle> ()IComparer(Of Circle) v jazyce Visual Basic). Však namísto předání IComparer<Circle>, předává v příkladu ShapeAreaComparer objekt, který implementuje IComparer<Shape>. V příkladu můžete předat porovnávací nástroj méně odvozeného typu (Shape) když kód volá pro porovnávací nástroj typu odvozený více (Circle), protože parametr typu IComparer<T> je obecné rozhraní contravariant.

Když nový Circle objekt přidán do SortedSet<Circle>, IComparer<Shape>.Compare Metoda (IComparer(Of Shape).Compare metody v jazyce Visual Basic) z ShapeAreaComparer objektu se říká pokaždé, když je porovnána nový element existujícího elementu. Typ parametru metody (Shape) menší odvozený typ, který je předávaný než (Circle), takže volání je typ bezpečné. Umožňuje Contravariance ShapeAreaComparer řazení kolekci jednoho typu, jakož i smíšené typy, které vyplývají z kolekce Shape.

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
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
 */

Zpět na začátek

Obecní delegáti s variantními parametry typu

V .NET Framework 4 obecní delegáti Func, jako jsou například Func<T, TResult>, mají kovariantní návratové typy a kontravariantní typy parametrů. Obecní delegáti Action, jako jsou 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é typy parametrů a (v případě obecných delgátů Func) méně odvozené návratové typy.

PoznámkaPoznámka

Poslední parametr obecného typu obecných delegátů Func určuje typ návratové hodnoty v hlavičce delegáta.Jedná se o kovariantní typ (klíčové slovo out), zatímco jiné parametry obecného typu jsou kontravariantní (klíčové slovo in).

Následující kód toto zobrazuje. První část kódu definuje třídu s názvem Base, třída s názvem Derived dědí z Base a další třída s metodou static (Shared v jazyce Visual Basic) MyMethod. Metoda přebírá instanci Base a vrátí instanci Derived. (Pokud je argument instance Derived, MyMethod ji vrátí; pokud je tento argument instance Base, MyMethod vrací novou instanci Derived.) V Main() je vytvořena instance Func<Base, Derived> (Func(Of Base, Derived) v jazyce Visual Basic), která představuje MyMethoda uloží ji do proměnné f1.

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
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;

Druhá část kódu ukazuje, že delegát může být přiřazen proměnné typu Func<Base, Base> (Func(Of Base, Base) v jazyce Visual Basic), protože návratový typ je kovariantní.

' Covariant return type.
Dim f2 As Func(Of Base, Base) = f1
Dim b2 As Base = f2(New Base())
// Covariant return type.
Func<Base, Base> f2 = f1;
Base b2 = f2(new Base());

Třetí část kódu ukazuje, že delegát může být přiřazen proměnné typu Func<Derived, Derived> (Func(Of Derived, Derived) v jazyce Visual Basic), protože typ parametru je kontravariantní.

' Contravariant parameter type.
Dim f3 As Func(Of Derived, Derived) = f1
Dim d3 As Derived = f3(New Derived())
// Contravariant parameter type.
Func<Derived, Derived> f3 = f1;
Derived d3 = f3(new Derived());

Poslední část kódu ukazuje, že delegát může být přiřazen proměnné typu Func<Derived, Base> (Func(Of Derived, Base) v jazyce Visual Basic) pomocí kombinování vlivů kontravariantního typu parametru a kovariantního návratového typu.

' Covariant return type and contravariant parameter type.
Dim f4 As Func(Of Derived, Base) = f1
Dim b4 As Base = f4(New Derived())
// Covariant return type and contravariant parameter type.
Func<Derived, Base> f4 = f1;
Base b4 = f4(new Derived());

Variance v obecných a neobecných delegátech

V předchozím kódu předpis MyMethod přesně odpovídá předpisu konstruované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 metod, které mají více odvozené typy parametrů a méně odvozené návratové typy, pokud jsou všechny typy delegáta zhotoveny z obecného typu delegáta Func<T, TResult>.

To je důležitý detail. Účinky kovariance a kontravariance v typových parametrech obecných delegátů jsou podobné účinkům kovariance a kontravariance v běžných vazbách delegátů (shlédněte Variance in Generic Delegates (C# and Visual Basic)). Variance ve vazbě delegáta však funguje se všemi typy delegáta, nejen s obecnými typy delegáta, které mají variantní typy parametrů. Variance ve vazbách delegátů navíc umožňuje metodě vázat se na jakéhokoli delegáta, který má více omezující typy parametrů 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 delegovátů jsou zhotoveny ze stejné definice obecného typu.

Následující příklad ukazuje kombinované účinky variance ve vazbě delegáta a variance u parametrů obecného typu. V příkladu je definována hierarchie typů, která zahrnuje tři typy od nejméně odvozeného (Type1) k nejvíce odvozenému (Type3). Variance se používá v běžných vazbách delegátů pro navázání metody s parametrem typu Type1 a návratovým typem Type3 k obecnému delegátovi s parametrem typu Type2 a návratovým typem Type2. Výsledný obecný delegát je poté přiřazen jiné proměnné, jejíž typ obecného delegáta má parametr typu Type3 a návratový typ Type1, pomocí kovariance a kontravariance parametrů obecného typu. Druhé přiřazení vyžaduje, aby typ proměnná i typ delegáta byly konstruovány ze stejné definice obecného typu, v tomto případě Func<T, TResult>.

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
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());
    }
}

Zpět na začátek

Definování variantních obecných rozhraní a delegátů

Počínaje .NET Framework 4 mají jazyky Visual Basic a C# klíčová slova, která umožňují označit parametry obecného typu rozhraní a delegátů jako kovariantní nebo kontravariantní.

PoznámkaPoznámka

Počínaje rozhraním .NET Framework verze 2.0 modul CLR (Common Language Runtime) podporuje anotace variance na parametrech obecného typu.Před .NET Framework 4 lze definování obecné třídy, která má tyto anotace, provést jediným způsobem a to za použítí jazyka MSIL (Microsoft Intermediate Language) kompilovat třídu pomocí Ilasm.exe (MSIL Assembler) nebo ji vydat v dynamickém sestavení.

Kovariantní typ parametru je označeno klíčovým slovem out (klíčové slovo Out v jazyce Visual Basic, + pro jazyk MSIL Assembler). Kovariantní typ parametru můžete použít jako návratovou hodnotu metody, které patří do rozhraní nebo jako návratový typ delegáta. Kovariantní typ parametru nelze použít jako obecný typ omezení pro metody rozhraní.

PoznámkaPoznámka

Pokud má metoda rozhraní parametr, který je typ obecného delegáta, kovariantní typ parametru typu rozhraní může být použit pro zadání kontravariantního typu parametru typu delegáta.

Kontravariantní typ parametru je označeno klíčovým slovem in (klíčové slovo In v jazyce Visual Basic, - pro jazyk MSIL Assembler). Kontravariantní typ parametru můžete použít jako typ parametru metody, která patří do rozhraní nebo jako typ parametru delegáta. Kontravariantní typ parametru lze použít jako obecný typ omezení pro metodu rozhraní.

Pouze typy rozhraní a typy delegáta mohou mít variantní typy parametrů. Typy rozhraní nebo delegátů mohou mít kovariantní i kontravariantní typové parametry.

Jazyky Visual Basic a C# neumožňují porušení pravidel pro použití kovariantních a kontravariantních typových parametrů nebo přidání anotací kovariance a kontravariance typovým parametrům jiných typů než rozhraní a delegát. jazyk MSIL Assembler neprovádí tyto kontroly, ale je vyvolána TypeLoadException, pokud se pokusíte načíst typ, který porušuje tato pravidla.

Informace a příklady kódu naleznete v tématu Variance in Generic Interfaces (C# and Visual Basic).

Zpět na začátek

Seznam variantních obecných typů rozhraní a delegátů

V .NET Framework 4, typy následující rozhraní a delegát mají covariant nebo parametry typu contravariant. 

Typ

Kovariantní typové poarametry

Kontravariantní typové parametry

Action<T>kAction<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>

Ano

Func<T, TResult>kFunc<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>

Ano

Zpět na začátek

Viz také

Koncepty

Variance in Generic Delegates (C# and Visual Basic)

Další zdroje

Kovariance a Contravariance (C# a Visual Basic)