Freigeben über


Kovarianz und Kontravarianz in Generika

Kovarianz und Kontravarianz sind Begriffe, die auf die Möglichkeit verweisen, einen abgeleiteten Typ (spezifischer) oder einen weniger abgeleiteten Typ (weniger spezifisch) als ursprünglich angegeben zu verwenden. Generische Typparameter unterstützen Kovarianz und Kontravarianz, um eine größere Flexibilität bei der Zuweisung und Verwendung generischer Typen zu bieten.

Wenn Sie auf ein Typsystem verweisen, weisen Kovarianz, Kontravarianz und Invarianz die folgenden Definitionen auf. In den Beispielen wird eine Basisklasse mit dem Namen Base und eine abgeleitete Klasse mit dem Namen Derivedangenommen.

  • Covariance

    Ermöglicht Ihnen die Verwendung eines weiter abgeleiteten Typs als ursprünglich angegeben.

    Sie können einer Variablen vom Typ IEnumerable<Derived>eine Instanz IEnumerable<Base> zuweisen.

  • Contravariance

    Ermöglicht die Verwendung eines generischeren (weniger abgeleiteten) Typs als ursprünglich angegeben.

    Sie können einer Variablen vom Typ Action<Base>eine Instanz Action<Derived> zuweisen.

  • Invariance

    Bedeutet, dass Sie nur den ursprünglich angegebenen Typ verwenden können. Ein invarianter generischer Typparameter ist weder kovariant noch kontravariant.

    Sie können einer Variablen vom Typ List<Base> keine Instanz von List<Derived> zuweisen oder umgekehrt.

Kovariante Typparameter ermöglichen es Ihnen, Zuordnungen vorzunehmen, die ähnlich wie gewöhnliche Polymorphismen aussehen, wie im folgenden Code gezeigt.

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

Die List<T> -Klasse implementiert die generische IEnumerable<T> -Schnittstelle. List<Derived> (List(Of Derived) in Visual Basic) implementiert daher IEnumerable<Derived>. Der kovariante Typparameter führt den Rest aus.

Kontravarianz hingegen scheint kontraintuitiv zu sein. Im folgenden Beispiel wird ein Delegat vom Typ Action<Base> (Action(Of Base) in Visual Basic) erstellt und anschließend einer Variablen vom Typ Action<Derived>zugewiesen.

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

Dies scheint rückwärts zu sein, aber es ist typsicherer Code, der kompiliert und ausgeführt wird. Der Lambda-Ausdruck stimmt mit dem Delegat überein, dem er zugewiesen ist. Daher wird eine Methode definiert, die einen Parameter vom Typ Base verwendet und keinen Rückgabewert aufweist. Der resultierende Delegat kann einer Variablen vom Typ Action<Derived> zugewiesen werden, da der Typparameter T des Action<T> Delegaten kontravariant ist. Der Code ist typsicher, da T er einen Parametertyp angibt. Wenn der Delegat des Typs Action<Base> aufgerufen wird, als ob es sich um einen Delegat vom Typ Action<Derived>handelt, muss das Argument vom Typ Derivedsein. Dieses Argument kann immer sicher an die zugrunde liegende Methode übergeben werden, da der Parameter der Methode vom Typ Baseist.

Im Allgemeinen lässt sich ein kovarianter Typparameter als Rückgabetyp eines Delegierten verwenden, während kontravariante Typparameter als Parametertypen dienen können. Für eine Schnittstelle können kovariante Typparameter als Rückgabetypen der Methoden der Schnittstelle verwendet werden, und kontravariante Typparameter können als Parametertypen der Methoden der Schnittstelle verwendet werden.

Kovarianz und Kontravarianz werden gemeinsam als Varianz bezeichnet. Ein generischer Typparameter, der nicht als kovariant oder kontravariant gekennzeichnet ist, wird als invariant bezeichnet. Eine kurze Zusammenfassung der Fakten über Unterschiede in der Common Language Runtime.

  • Variant-Typparameter sind auf generische Schnittstellen- und generische Delegattypen beschränkt.

  • Eine generische Schnittstelle oder ein generischer Delegattyp kann sowohl über kovariante als auch über kontravariante Typparameter verfügen.

  • Die Varianz gilt nur für Bezugstypen; Wenn Sie einen Werttyp für einen Variant-Typ-Parameter angeben, ist dieser Typparameter für den resultierenden konstruierten Typ invariant.

  • Varianz gilt nicht für eine Delegatkombination. Das heißt, bei zwei Delegaten von Typen Action<Derived> und Action<Base> (Action(Of Derived) und Action(Of Base) in Visual Basic) können Sie den zweiten Delegat nicht mit dem ersten kombinieren, obwohl das Ergebnis typsicher wäre. Bei Varianz kann der zweite Delegat einer Variable des Action<Derived>-Typs zugewiesen werden, Delegaten können aber nur kombiniert werden, wenn ihre Typen genau überstimmen.

  • Ab C# 9 werden kovariante Rückgabetypen unterstützt. Eine überschreibende Methode kann einen stärker abgeleiteten Rückgabetyp deklarieren als die Methode, die überschrieben wird, und eine überschreibende schreibgeschützte Eigenschaft kann einen stärker abgeleiteten Typ deklarieren.

Generische Schnittstellen mit kovarianten Typparametern

Mehrere generische Schnittstellen weisen kovariante Typparameter auf, z. B. , IEnumerable<T>, IEnumerator<T>, IQueryable<T>und IGrouping<TKey,TElement>. Alle Typparameter dieser Schnittstellen sind kovariant, sodass sie nur für die Rückgabetypen der Member verwendet werden.

Im folgenden Beispiel werden kovariante Typparameter veranschaulicht. Im Beispiel werden zwei Typen definiert: Base hat eine statische Methode mit dem Namen PrintBases, die ein IEnumerable<Base> (IEnumerable(Of Base) in Visual Basic) übernimmt und die Elemente ausgibt. Derived erbt von Base. Im Beispiel wird ein leeres List<Derived> (List(Of Derived) in Visual Basic) erstellt und es wird veranschaulicht, dass dieser Typ an PrintBases übergeben und ohne Umwandlung einer Variablen vom Typ IEnumerable<Base> zugewiesen werden kann. List<T> implementiert IEnumerable<T>, das einen einzigen kovarianten Typparameter hat. Der kovariante Typparameter ist der Grund, warum eine Instanz von IEnumerable<Derived> anstelle von IEnumerable<Base> verwendet werden kann.

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

Generische Schnittstellen mit kontravarianten Typparametern

Mehrere generische Schnittstellen weisen kontravariante Typparameter auf; Beispiel: IComparer<T>, , IComparable<T>und IEqualityComparer<T>. Diese Schnittstellen weisen nur kontravariante Typparameter auf, sodass die Typparameter nur als Parametertypen in den Membern der Schnittstellen verwendet werden.

Im folgenden Beispiel werden kontravariante Typparameter veranschaulicht. Im Beispiel wird eine abstrakte Klasse (MustInherit in Visual Basic) Shape mit einer Area Eigenschaft definiert. Das Beispiel definiert auch eine ShapeAreaComparer Klasse, die implementiert IComparer<Shape> (IComparer(Of Shape) in Visual Basic). Die Implementierung der IComparer<T>.Compare Methode basiert auf dem Wert der Area Eigenschaft, sodass ShapeAreaComparer Objekte nach Fläche Shape sortiert werden können.

Die Circle Klasse erbt Shape und überschreibt Area. Im Beispiel wird ein SortedSet<T> von Circle -Objekten erstellt, wobei ein Konstruktor verwendet wird, der IComparer<Circle> (IComparer(Of Circle) in Visual Basic) akzeptiert. Anstatt jedoch ein IComparer<Circle> Objekt zu übergeben, übergibt das Beispiel ein ShapeAreaComparer Objekt, welches IComparer<Shape> implementiert. Das Beispiel kann einen Vergleichsoperator eines weniger abgeleiteten Typs (Shape) übergeben, wenn der Code einen Vergleichsoperator eines stärker abgeleiteten Typs erfordert (Circle), da der Typparameter der IComparer<T> generischen Schnittstelle kontravariant ist.

Jedes Mal, wenn ein neues Circle Objekt dem SortedSet<Circle> hinzugefügt wird und mit einem vorhandenen Element verglichen wird, wird die IComparer<Shape>.Compare Methode (IComparer(Of Shape).Compare Methode in Visual Basic) des ShapeAreaComparer Objekts aufgerufen. Der Parametertyp der Methode (Shape) ist kleiner als der Typ, der übergeben wird (Circle), sodass der Aufruf sicher ist. Kontravarianz ermöglicht es ShapeAreaComparer , eine Auflistung eines einzelnen Typs sowie eine Auflistung von gemischten Typen zu sortieren, die von Shapeabgeleitet werden.

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

Generische Delegaten mit Variantentypparametern

Die Func generischen Delegaten, wie Func<T,TResult>, haben kovariante Rückgabetypen und kontravariante Parametertypen. Die Action generischen Delegaten, wie Action<T1,T2>, weisen kontravariante Parametertypen auf. Dies bedeutet, dass den Stellvertretungen Variablen zugewiesen werden können, die mehr abgeleitete Parametertypen haben und (im Fall der Func generischen Stellvertretungen) weniger abgeleitete Rückgabetypen.

Hinweis

Der letzte generische Typparameter der generischen Func -Delegaten gibt den Typ des Rückgabewerts in der Signatur des Delegaten an. Es ist kovariant (out Schlüsselwort), während die anderen generischen Typparameter kontravariant (in Schlüsselwort) sind.

Der folgende Code veranschaulicht dies. Der erste Codeabschnitt definiert eine Klasse namens Base, eine Klasse mit dem Namen Derived , die erbt Base, und eine andere Klasse mit einer static Methode (Shared in Visual Basic) mit dem Namen MyMethod. Die Methode nimmt eine Instanz von Base und gibt eine Instanz von Derived zurück. (Wenn es sich bei dem Argument um eine Instanz von Derived, MyMethod gibt sie zurück; wenn das Argument eine Instanz von Baseist , MyMethod gibt eine neue Instanz von Derived.) In Main(), erstellt das Beispiel eine Instanz von Func<Base, Derived> (Func(Of Base, Derived) in Visual Basic), die darstellt MyMethod, und speichert sie in der Variablen 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

Der zweite Codeabschnitt zeigt, dass der Delegat einer Variablen vom Typ Func<Base, Base> (Func(Of Base, Base) in Visual Basic) zugewiesen werden kann, da der Rückgabetyp kovariant ist.

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

Der dritte Codeabschnitt zeigt, dass der Delegat einer Variablen vom Typ Func<Derived, Derived> (Func(Of Derived, Derived) in Visual Basic) zugewiesen werden kann, da der Parametertyp kontravariant ist.

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

Der letzte Codeabschnitt zeigt, dass der Delegat einer Variablen vom Typ Func<Derived, Base> (Func(Of Derived, Base) in Visual Basic) zugewiesen werden kann, wobei die Effekte des kontravarianten Parametertyps und des kovarianten Rückgabetyps kombiniert werden.

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

Abweichung bei nicht generischen Delegaten

Im vorherigen Code entspricht die Signatur von MyMethod genau der Signatur des konstruierenden generischen Delegaten Func<Base, Derived> (Func(Of Base, Derived) in Visual Basic). Das Beispiel zeigt, dass dieser generische Delegat in Variablen oder Methodenparametern gespeichert werden kann, die über abgeleitete Parametertypen und weniger abgeleitete Rückgabetypen verfügen, solange alle Delegattypen aus dem generischen Delegattyp Func<T,TResult>erstellt werden.

Dies ist ein wichtiger Punkt. Kovarianz und Kontravarianz haben in den Typparametern generischer Delegaten ähnliche Auswirkungen wie bei der normalen Delegatbindung (siehe Varianz in Delegaten (C#) und Varianz in Delegaten (Visual Basic)). Die Varianz bei der Delegatbindung funktioniert jedoch bei allen Delegattypen und nicht nur bei generischen Delegattypen, die über variante Typparameter verfügen. Darüber hinaus ermöglicht die Abweichung in der Stellvertretungsbindung, dass eine Methode an jeden Delegaten gebunden werden kann, der restriktivere Parametertypen und einen weniger restriktiven Rückgabetyp aufweist, während die Zuweisung generischer Stellvertretungen nur funktioniert, wenn beide Delegattypen aus derselben generischen Typdefinition erstellt werden.

Das folgende Beispiel zeigt die kombinierten Auswirkungen der Varianz in der Stellvertretungsbindung und Varianz in generischen Typparametern. Im Beispiel wird eine Typhierarchie definiert, die drei Typen enthält, von den am wenigsten abgeleiteten (Type1) bis zum meisten abgeleiteten (Type3). Die Varianz bei der Bindung eines normalen Delegaten wird verwendet, um eine Methode mit einem Parametertyp Type1 und einem Rückgabewert vom Typ Type3 an einen generischen Delegaten mit einem Parametertyp Type2 und einem Rückgabewert vom Typ Type2 zu binden. Der resultierende generische Delegat wird dann einer anderen Variablen zugewiesen, deren generischer Delegattyp einen Parameter vom Typ Type3 und einen Rückgabetyp Type1 hat, unter Verwendung der Kovarianz und Kontravarianz von generischen Typparametern. Für die zweite Zuordnung muss sowohl der Variabletyp als auch der Delegattyp aus derselben generischen Typdefinition erstellt werden, in diesem Fall 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

Definieren von generischen Variantenschnittstellen und Delegaten

Visual Basic und C# verfügen über Schlüsselwörter, mit denen Sie die generischen Typparameter von Schnittstellen und Delegaten als kovariant oder kontravariant kennzeichnen können.

Ein kovarianter Typparameter wird mit dem out Schlüsselwort (Out Schlüsselwort in Visual Basic) markiert. Sie können einen kovarianten Typparameter als Rückgabewert einer Methode verwenden, die zu einer Schnittstelle gehört, oder als Rückgabetyp eines Delegaten. Sie können keinen kovarianten Typparameter als generische Typeinschränkung für Schnittstellenmethoden verwenden.

Hinweis

Wenn eine Methode einer Schnittstelle über einen Parameter verfügt, der ein generischer Delegattyp ist, kann ein kovarianter Typparameter des Schnittstellentyps verwendet werden, um einen kontravarianten Typparameter des Delegatentyps anzugeben.

Ein kontravarianter Typparameter wird mit dem in Schlüsselwort (In Schlüsselwort in Visual Basic) gekennzeichnet. Sie können einen kontravarianten Typparameter als Typ eines Parameters einer Methode verwenden, die zu einer Schnittstelle gehört, oder als Typ eines Parameters eines Delegaten. Sie können einen Kontravariantentypparameter als generische Typeinschränkung für eine Schnittstellenmethode verwenden.

Nur Schnittstellentypen und Delegattypen können Variantentypparameter aufweisen. Eine Schnittstelle oder ein Delegattyp kann sowohl kovariant als auch kontravariante Typparameter aufweisen.

Visual Basic und C# erlauben es Ihnen nicht, die Regeln für die Verwendung von Kovarianten- und Kontravariantentypparametern zu verletzen oder den Typparametern anderer Typen als Schnittstellen und Delegaten Kovarianz- und Kontravarianzanmerkungen hinzuzufügen.

Informationen und Beispielcode finden Sie unter Varianz in generischen Schnittstellen (C#) und Varianz in Generic Interfaces (Visual Basic).For information and example code, see Variance in Generic Interfaces (Visual Basic)

Liste der Typen

Die folgenden Schnittstellen- und Delegattypen weisen kovariante und/oder kontravariante Typparameter auf.

Typ Kovariante Typparameter Kontravariante Typparameter
Action<T> bis 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> bis 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

Siehe auch