Dela via


Kovarians och kontravarians i generiska läkemedel

Kovarians och kontravarians är termer som refererar till möjligheten att använda en mer härledd typ (mer specifik) eller en mindre härledd typ (mindre specifik) än vad som ursprungligen angavs. Generiska typparametrar stöder kovarians och kontravarians för att ge större flexibilitet vid tilldelning och användning av generiska typer.

När du refererar till ett typsystem har covariance, contravariance och invariance följande definitioner. Exemplen förutsätter en basklass med namnet Base och en härledd klass med namnet Derived.

  • Covariance

    Gör att du kan använda en mer härledd typ än vad som ursprungligen angavs.

    Du kan tilldela en instans av IEnumerable<Derived> till en variabel av typen IEnumerable<Base>.

  • Contravariance

    Gör att du kan använda en mer allmän (mindre härledd) typ än vad som ursprungligen angavs.

    Du kan tilldela en instans av Action<Base> till en variabel av typen Action<Derived>.

  • Invariance

    Innebär att du bara kan använda den typ som ursprungligen angavs. En invariant generisk typparameter är varken covariant eller kontravariant.

    Du kan inte tilldela en instans av List<Base> till en variabel av typen List<Derived> eller vice versa.

Med parametrar av typen Covariant kan du göra tilldelningar som liknar vanlig polymorfism, som du ser i följande kod.

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

Klassen List<T> implementerar IEnumerable<T> gränssnittet, så List<Derived> (List(Of Derived) i Visual Basic) implementerar IEnumerable<Derived>. Parametern covarianttyp utför resten.

Kontravarians verkar å andra sidan kontraintuitivt. I följande exempel skapas ett ombud av typen Action<Base> (Action(Of Base) i Visual Basic) och tilldelas sedan ombudet till en variabel av typen 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())

Detta verkar bakåt, men det är typsäker kod som kompileras och körs. Lambda-uttrycket matchar det ombud som det tilldelas till, så det definierar en metod som tar en parameter av typen Base och som inte har något returvärde. Det resulterande ombudet kan tilldelas en variabel av typen Action<Derived> eftersom typparametern T för ombudet Action<T> är kontravariant. Koden är typsäker eftersom T anger en parametertyp. När ombudet av typen Action<Base> anropas som om det vore ett ombud av typen Action<Derived>måste argumentet vara av typen Derived. Det här argumentet kan alltid skickas säkert till den underliggande metoden eftersom metodens parameter är av typen Base.

I allmänhet kan en parameter av typen covariant användas som returtyp för ett ombud, och parametrar av typen contravariant kan användas som parametertyper. För ett gränssnitt kan parametrar av typen covariant användas som returtyper för gränssnittets metoder, och parametrar av typen contravariant kan användas som parametertyper för gränssnittets metoder.

Kovarians och kontravariation kallas gemensamt varians. En generisk typparameter som inte är markerad som covariant eller kontravariant kallas invariant. En kort sammanfattning av fakta om varians i den vanliga språkkörningen:

  • Parametrar av varianttyp är begränsade till generiska gränssnitt och generiska ombudstyper.

  • Ett allmänt gränssnitt eller en allmän ombudstyp kan ha parametrar av typen covariant och contravariant.

  • Varians gäller endast för referenstyper. Om du anger en värdetyp för en parameter av varianttyp är den typparametern invariant för den resulterande konstruerade typen.

  • Variansen gäller inte för delegerad kombination. Med tanke på två ombud av typer Action<Derived> och Action<Base> (Action(Of Derived) och Action(Of Base) i Visual Basic) kan du inte kombinera det andra ombudet med det första, även om resultatet skulle vara typsäkert. Avvikelse gör att det andra ombudet kan tilldelas till en variabel av typen Action<Derived>, men ombud kan bara kombineras om deras typer matchar exakt.

  • Från och med C# 9 stöds samtidiga returtyper. En övergripande metod kan deklarera en mer härledd returtyp som den åsidosätter, och en åsidosättande skrivskyddad egenskap kan deklarera en mer härledd typ.

Allmänna gränssnitt med parametrar av typen covariant

Flera generiska gränssnitt har parametrar av typen covariant, IEnumerable<T>till exempel , IEnumerator<T>, IQueryable<T>och IGrouping<TKey,TElement>. Alla typparametrar för dessa gränssnitt är samvarianta, så typparametrarna används endast för medlemmarnas returtyper.

I följande exempel visas parametrar av typen covariant. Exemplet definierar två typer: Base har en statisk metod med namnet PrintBases som tar en IEnumerable<Base> (IEnumerable(Of Base) i Visual Basic) och skriver ut elementen. Derived ärver från Base. Exemplet skapar en tom List<Derived> (List(Of Derived) i Visual Basic) och visar att den här typen kan skickas till PrintBases och tilldelas till en variabel av typen IEnumerable<Base> utan gjutning. List<T> implementerar IEnumerable<T>, som har en enda parameter av typen covariant. Parametern covarianttyp är orsaken till att en instans av IEnumerable<Derived> kan användas i stället för 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

Generiska gränssnitt med parametrar av typen contravariant

Flera generiska gränssnitt har parametrar av typen contravariant; till exempel: IComparer<T>, IComparable<T>och IEqualityComparer<T>. Dessa gränssnitt har endast parametrar av typen contravariant, så typparametrarna används endast som parametertyper i gränssnittens medlemmar.

I följande exempel visas parametrar av typen contravariant. Exemplet definierar en abstrakt klass (MustInherit i Visual Basic) Shape med en Area egenskap. Exemplet definierar också en ShapeAreaComparer klass som implementerar IComparer<Shape> (IComparer(Of Shape) i Visual Basic). Implementeringen av IComparer<T>.Compare metoden baseras på värdet för Area egenskapen, så ShapeAreaComparer kan användas för att sortera Shape objekt efter område.

Klassen Circle ärver och åsidosätter ShapeArea. Exemplet skapar en SortedSet<T> av objekten med hjälp av Circle en konstruktor som tar en IComparer<Circle> (IComparer(Of Circle) i Visual Basic). Men i stället för att skicka ett IComparer<Circle>, skickar exemplet ett ShapeAreaComparer objekt som implementerar IComparer<Shape>. Exemplet kan skicka en jämförelse av en mindre härledd typ (Shape) när koden anropar en jämförelse av en mer härledd typ (Circle), eftersom typparametern för det IComparer<T> generiska gränssnittet är kontravariant.

När ett nytt Circle objekt läggs till SortedSet<Circle>IComparer<Shape>.Compare i anropas -metoden (IComparer(Of Shape).Compare -metoden i Visual Basic) ShapeAreaComparer för objektet varje gång det nya elementet jämförs med ett befintligt element. Parametertypen för metoden (Shape) är mindre härledd än den typ som skickas (Circle), så anropet är typsäkert. Contravariance gör det möjligt ShapeAreaComparer att sortera en samling av en enskild typ, samt en blandad samling av typer, som härleds från 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

Allmänna ombud med parametrar av varianttyp

De Func allmänna ombuden, till exempel Func<T,TResult>, har covarianta returtyper och kontravarianta parametertyper. De Action allmänna ombuden, till exempel Action<T1,T2>, har kontravarianta parametertyper. Det innebär att ombuden kan tilldelas till variabler som har fler härledda parametertyper och (i fallet med de allmänna ombuden Func ) mindre härledda returtyper.

Kommentar

Den sista generiska typparametern för de Func generiska ombuden anger typen av returvärde i delegatsignaturen. Det är covariant (out nyckelord), medan de andra generiska typparametrarna är kontravarianta (in nyckelord).

Följande kod illustrerar detta. Den första koddelen definierar en klass med namnet Base, en klass med namnet Derived som ärver Baseoch en annan klass med en static metod (Shared i Visual Basic) med namnet MyMethod. Metoden tar en instans av Base och returnerar en instans av Derived. (Om argumentet är en instans av DerivedMyMethod returnerar det. Om argumentet är en instans av BaseMyMethod returnerar en ny instans av Derived.) I Main()skapar exemplet en instans av Func<Base, Derived> (Func(Of Base, Derived) i Visual Basic) som representerar MyMethodoch lagrar den i variabeln 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

Den andra koddelen visar att ombudet kan tilldelas till en variabel av typen Func<Base, Base> (Func(Of Base, Base) i Visual Basic), eftersom returtypen är samvariant.

// 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())

Den tredje koddelen visar att ombudet kan tilldelas till en variabel av typen Func<Derived, Derived> (Func(Of Derived, Derived) i Visual Basic), eftersom parametertypen är kontravariant.

// 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())

Den sista koddelen visar att ombudet kan tilldelas en variabel av typen Func<Derived, Base> (Func(Of Derived, Base) i Visual Basic), som kombinerar effekterna av parametertypen contravariant och returtypen covariant.

// 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())

Varians i icke-generiska ombud

I föregående kod matchar signaturen MyMethod för exakt signaturen för det konstruerade generiska ombudet: Func<Base, Derived> (Func(Of Base, Derived) i Visual Basic). Exemplet visar att det här allmänna ombudet kan lagras i variabler eller metodparametrar som har fler härledda parametertyper och mindre härledda returtyper, så länge alla ombudstyper skapas från den generiska delegattypen Func<T,TResult>.

Detta är en viktig punkt. Effekterna av kovarians och kontravarians i typparametrarna för generiska ombud liknar effekterna av samvarians och kontravarians i vanlig delegatbindning (se Varians i ombud (C#) och varians i ombud (Visual Basic)). Variansen i ombudsbindningen fungerar dock med alla ombudstyper, inte bara med generiska delegattyper som har parametrar av varianttyp. Dessutom gör variansen i ombudsbindningen att en metod kan bindas till alla ombud som har mer restriktiva parametertyper och en mindre restriktiv returtyp, medan tilldelningen av generiska ombud endast fungerar om båda delegattyperna konstrueras från samma generiska typdefinition.

I följande exempel visas de kombinerade effekterna av varians i ombudsbindning och varians i parametrar av allmän typ. Exemplet definierar en typhierarki som innehåller tre typer, från minst härledda (Type1) till de flesta härledda (Type3). Varians i vanlig delegatbindning används för att binda en metod med en parametertyp av Type1 och en returtyp Type3 för till ett allmänt ombud med parametertypen Type2 och en returtyp av Type2. Det resulterande allmänna ombudet tilldelas sedan till en annan variabel vars generiska delegattyp har en parameter av typen Type3 och en returtyp av , med hjälp av Type1kovarians och kontravarians för generiska typparametrar. Den andra tilldelningen kräver att både variabeltypen och ombudstypen skapas från samma generiska typdefinition, i det här fallet Func<T,TResult>.

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

Definiera generiska variantgränssnitt och ombud

Visual Basic och C# har nyckelord som gör att du kan markera de generiska typparametrarna för gränssnitt och ombud som covarianta eller kontravarianta.

En parameter av typen covariant markeras med nyckelordet out (Out nyckelordet i Visual Basic). Du kan använda en parameter av typen covariant som returvärde för en metod som tillhör ett gränssnitt eller som returtyp för ett ombud. Du kan inte använda en parameter av typen covariant som en allmän typbegränsning för gränssnittsmetoder.

Kommentar

Om en metod i ett gränssnitt har en parameter som är en allmän delegattyp kan en parameter av typen covarianttyp av gränssnittstyp användas för att ange en parameter av typen contravarianttyp.

En parameter av typen contravariant markeras med nyckelordet in (In nyckelordet i Visual Basic). Du kan använda en parameter av typen contravariant som typ av en parameter för en metod som tillhör ett gränssnitt, eller som typ av en parameter för ett ombud. Du kan använda en parameter av typen contravariant som en allmän typbegränsning för en gränssnittsmetod.

Endast gränssnittstyper och ombudstyper kan ha parametrar av varianttyp. Ett gränssnitt eller en ombudstyp kan ha parametrar av typen covariant och contravariant.

Visual Basic och C# tillåter inte att du bryter mot reglerna för att använda parametrar av typen covariant och contravariant, eller lägga till covariance- och kontravariantanteckningar till typparametrarna för andra typer än gränssnitt och ombud.

Information och exempelkod finns i Varians i Generiska gränssnitt (C#) och Varians i Allmänna gränssnitt (Visual Basic).

Lista över typer

Följande gränssnitts- och ombudstyper har parametrar av typen covariant och/eller contravariant.

Typ Parametrar för covarianttyp Parametrar av typen Contravariant
Action<T> till Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16> Ja
Comparison<T> Ja
Converter<TInput,TOutput> Ja Ja
Func<TResult> Ja
Func<T,TResult> till Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult> Ja Ja
IComparable<T> Ja
Predicate<T> Ja
IComparer<T> Ja
IEnumerable<T> Ja
IEnumerator<T> Ja
IEqualityComparer<T> Ja
IGrouping<TKey,TElement> Ja
IOrderedEnumerable<TElement> Ja
IOrderedQueryable<T> Ja
IQueryable<T> Ja

Se även