Markieren Sie das Kontrollkästchen Englisch, um die englische Version dieses Artikels anzuzeigen. Sie können den englischen Text auch in einem Popup-Fenster einblenden, indem Sie den Mauszeiger über den Text bewegen.
Übersetzung
Englisch

Kovarianz und Kontravarianz in Generika

.NET Framework (current version)
 

Kovarianz und Kontravarianz sind Begriffe, die auf die Fähigkeit Bezug nehmen, einen weniger stark abgeleiteten (allgemeineren) oder einen stärker abgeleiteten (spezifischeren) Typ zu verwenden als ursprünglich angegeben. Generische Typparameter unterstützen Kovarianz und Kontravarianz und bieten somit mehr Flexibilität beim Zuweisen und Verwenden von generischen Typen. Wenn Sie auf ein Typsystem verweisen, haben Kovarianz, Kontravarianz und Invarianz die folgenden Definitionen. In den Beispielen wird von der Basisklasse Base und der abgeleiteten Klasse Derived ausgegangen.

  • Covariance

    Ermöglicht die Verwendung eines stärker abgeleiteten Typs als ursprünglich angegeben.

    Sie können eine Instanz von IEnumerable<Derived> (IEnumerable(Of Derived) in Visual Basic) einer Variablen des Typs IEnumerable<Base> zuweisen.

  • Contravariance

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

    Sie können eine Instanz von IEnumerable<Base> (IEnumerable(Of Base) in Visual Basic) einer Variablen des Typs IEnumerable<Derived> zuweisen.

  • Invariance

    Bedeutet, dass nur der ursprünglich angegebene Typ verwendet werden kann. Ein invarianter generischer Typparameter ist also weder kovariant noch kontravariant.

    Sie können eine Instanz von IEnumerable<Base> (IEnumerable(Of Base) in Visual Basic) nicht einer Variablen des Typs IEnumerable<Derived> zuweisen oder umgekehrt.

Kovariante Typparameter ermöglichen es Ihnen, Zuweisungen vorzunehmen, die normaler Polymorphismus (C#-Programmierhandbuch) sehr ähneln, wie im folgenden Code dargestellt.

IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = 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 ist für den Rest zuständig.

Kontravarianz erscheint dagegen kontraintuitiv. Im folgenden Beispiel wird ein Delegat des Action<Base>-Typs (Action(Of Base) in Visual Basic) erstellt und dann einer Variable des Action<Derived>-Typs zugewiesen.

Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());

Das erscheint rückständig, ist aber typsicherer Code, der kompiliert und ausgeführt wird. Der Lambda-Ausdruck entspricht dem ihm zugewiesenen Delegaten und definiert daher eine Methode, die einen Parameter des Base-Typs akzeptiert und keinen Wert zurückgibt. Der resultierende Delegat kann einer Variable des Action<Derived>-Typs zugewiesen werden, da der Typparameter T des Action<T>-Delegaten kontravariant ist. Der Code ist typsicher, da T einen Parametertyp angibt. Wenn der Delegat des Action<Base>-Typs wie ein Delegat des Action<Derived>-Typs aufgerufen wird, muss sein Argument vom Derived-Typ sein. Dieses Argument kann immer sicher an die zugrunde liegende Methode übergeben werden, da der Parameter der Methode vom Base-Typ ist.

Im Allgemeinen können kovariante Typparameter als Rückgabetyp eines Delegaten und kontravariante Typparameter als Parametertypen verwendet werden. Für eine Schnittstelle können kovariante Typparameter als Rückgabetypen der Methoden der Schnittstelle verwendet werden und kontravariante Typparameter als Parametertypen der Methoden der Schnittstelle.

Kovarianz und Kontravarianz werden zusammen als Varianz bezeichnet. Ein generischer Typparameter, der nicht als kovariant oder kontravariant markiert ist, wird als invariant bezeichnet. Im Folgenden sehen Sie eine kurze Zusammenfassung der Fakten zur Varianz in der Common Language Runtime:

  • In .NET Framework 4sind variante Typparameter auf generische Schnittstellen und generische Delegattypen beschränkt.

  • Eine generische Schnittstelle oder ein generischer Delegattyp kann sowohl kovariante, als auch kontravariante Typparameter haben.

  • Varianz gilt nur für Verweistypen. Wenn Sie für einen varianten Typparameter einen Werttyp angeben, ist dieser Typparameter für den resultierenden konstruierten Typ invariant.

  • Varianz gilt nicht für eine Delegatkombination. Bei zwei Delegaten vom Typ Action<Derived> und Action<Base> (Action(Of Derived) und Action(Of Base) in Visual Basic) können Sie folglich den zweiten Delegaten 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.

In den folgenden Unterabschnitten werden Kovariante und kontravariante Typparameter ausführlich beschrieben:

Beginnend mit .NET Framework 4 verfügen mehrere generische Schnittstellen über kovariante Typparameter, 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 verfügt über eine statische Methode mit dem Namen PrintBases, die einen IEnumerable<Base> (IEnumerable(Of Base) in Visual Basic) erwartet und die Elemente ausgibt. Derived erbt von Base. Im Beispiel wird ein leerer List<Derived> (List(Of Derived) in Visual Basic) erstellt, um zu veranschaulichen, dass dieser Typ an PrintBases übergeben und ohne Umwandlung einer Variablen vom Typ IEnumerable<Base> zugewiesen werden kann. List<T> implementiert IEnumerable<T>, die über einen einzelnen kovarianten Typparameter verfügt. Aufgrund des kovarianten Typparameters kann eine Instanz von IEnumerable<Derived> anstelle von IEnumerable<Base> verwendet werden.

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

Zurück nach oben

Beginnend mit .NET Framework 4 verfügen mehrere generische Schnittstellen über kontravariante Typparameter, z. B. IComparer<T>, IComparable<T>, and IEqualityComparer<T>. Diese Schnittstellen verfügen nur über kontravariante Typparameter. Die Typparameter werden daher nur in den Membern der Schnittstellen als Parametertypen verwendet. 

Im folgenden Beispiel werden kontravariante Typparameter veranschaulicht. Im Beispiel wird eine abstrakte (MustInherit in Visual Basic) Shape-Klasse mit einer Area-Eigenschaft definiert. Außerdem wird eine ShapeAreaComparer-Klasse definiert, die IComparer<Shape> (IComparer(Of Shape) in Visual Basic) implementiert. Die Implementierung der IComparer<T>.Compare-Methode basiert auf dem Wert der Area-Eigenschaft, sodass ShapeAreaComparer zum Sortieren von Shape-Objekten nach Bereich verwendet werden kann.

Die Circle-Klasse erbt von 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. Anstelle von IComparer<Circle> wird jedoch ein ShapeAreaComparer-Objekt übergeben, das IComparer<Shape> implementiert. Im Beispiel kann ein Vergleich eines weniger stark abgeleiteten Typs (Shape) übergeben werden, wenn der Code einen Vergleich eines stärker abgeleiteten Typs (Circle) verlangt, da der Typparameter der generischen IComparer<T>-Schnittstelle kontravariant ist.

Wenn Circle ein neues SortedSet<Circle>-Objekt hinzugefügt wird, wird die IComparer<Shape>.Compare-Methode (IComparer(Of Shape).Compare-Methode in Visual Basic) des ShapeAreaComparer-Objekts immer dann aufgerufen, wenn das neue Element mit einem vorhandenen Element verglichen wird. Der Parametertyp der Methode (Shape) ist weniger stark abgeleitet als der Typ, der übergeben wird (Circle). Deshalb ist der Aufruf typsicher. Kontravarianz ermöglicht es ShapeAreaComparer, eine Auflistung eines einzelnen Typs sowie eine Auflistung von gemischten Typen zu sortieren, die von Shape abgeleitet 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
 */

Zurück nach oben

In .NET Framework 4 verfügen die generischen Func-Delegaten (z. B. Func<T, TResult>) über kovariante Rückgabetypen und über kontravariante Parametertypen. Die generischen Action-Delegaten, z. B. Action<T1, T2>, verfügen über kontravariante Parametertypen. Das bedeutet, dass die Delegaten Variablen mit weiter abgeleiteten Parametertypen und (im Fall von generischen Func-Delegaten) weniger abgeleiteten Rückgabetypen zugewiesen werden können.

System_CAPS_noteHinweis

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

Dies wird im folgenden Code veranschaulicht. Im ersten Codeabschnitt wird eine Klasse mit dem Namen Base, eine Klasse mit dem Namen Derived, die Base erbt, und eine weitere Klasse mit einer static-Methode (Shared in Visual Basic) mit dem Namen MyMethod definiert. Die Methode akzeptiert eine Instanz von Base und gibt eine Instanz von Derived zurück. (Wenn das Argument eine Instanz von Derived ist, gibt MyMethod diese zurück. Wenn das Argument eine Instanz von Base ist, gibt MyMethod eine neue Instanz von Derived zurück.) Im Beispiel wird in Main() eine Instanz von Func<Base, Derived> (Func(Of Base, Derived) in Visual Basic) erstellt, die MyMethod darstellt und in der Variablen f1 gespeichert wird.

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;

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

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

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 Auswirkungen des kontravarianten Parametertyps und des kovarianten Rückgabewerttyps kombiniert werden.

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

Im obigen Code passt die Signatur von MyMethod exakt zur Signatur des erstellten generischen Delegaten Func<Base, Derived> (Func(Of Base, Derived) in Visual Basic). Das Beispiel zeigt, dass dieser generische Delegat in Variablen oder Methodenparametern mit weiter abgeleiteten Parametertypen und weniger abgeleiteten Rückgabetypen gespeichert werden kann, 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 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 kann durch die Varianz bei der Delegatbindung eine Methode an einen beliebigen Delegaten gebunden werden, der restriktivere Parametertypen und einen weniger restriktiven Rückgabetyp verwendet, wohingegen die Zuweisung generischer Delegaten nur funktioniert, wenn beide Delegattypen aus der gleichen generischen Typdefinition erstellt wurden.

Im folgenden Beispiel werden die kombinierten Effekte von Varianz in der Delegatbindung und Varianz bei generischen Typparametern veranschaulicht. Im Beispiel wird eine Typhierarchie definiert, die drei Typen beinhaltet, vom am wenigsten abgeleiteten Typ (Type1) bis zum am weitesten abgeleiteten Typ (Type3). Bei der normalen Delegatbindung wird die Varianz verwendet, um eine Methode mit dem Parametertyp Type1 und dem Rückgabetyp Type3 an einen generischen Delegaten mit dem Parametertyp Type2 und dem Rückgabetyp Type2 zu binden. Der resultierende generische Delegat wird anschließend einer anderen Variablen zugewiesen, deren generischer Delegattyp einen Parameter vom Typ Type3 und den Rückgabetyp Type1 hat. Hierbei werden Kovarianz und Kontravarianz generischer Typparameter verwendet. Bei der zweiten Zuweisung müssen sowohl der Variablentyp als auch der Delegattyp mit der gleichen 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());
    }
}

Zurück nach oben

Beginnend mit .NET Framework 4 verfügen Visual Basic und C# über Schlüsselwörter, mit denen die generischen Typparameter von Schnittstellen und Delegaten als kovariant oder kontravariant gekennzeichnet werden können.

System_CAPS_noteHinweis

Beginnen mit .NET Framework, Version 2.0, unterstützt die Common Language Runtime Varianzkennzeichnungen für generische Typparameter. Vor .NET Framework 4 war Microsoft Intermediate Language (MSIL) die einzige Möglichkeit, eine generische Klasse zu definieren, die über diese Kennzeichnungen verfügt, entweder durch das Kompilieren der Klasse mit Ilasm.exe (IL Assembler) oder durch das Ausgeben in einer dynamischen Assembly.

Ein kovarianter Typparameter wird mit dem out-Schlüsselwort (Out-Schlüsselwort in Visual Basic, + für den MSIL-Assembler) gekennzeichnet. 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 einen kovarianten Typparameter nicht als generische Typeinschränkung für Schnittstellenmethoden verwenden.

System_CAPS_noteHinweis

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 Delegattyps anzugeben.

Ein kontravarianter Typparameter wird mit dem in-Schlüsselwort (In-Schlüsselwort in Visual Basic, - für den MSIL-Assembler) 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 kontravarianten Typparameter als generische Typeinschränkung für Schnittstellenmethoden verwenden.

Nur Schnittstellentypen und Delegattypen können über variante Typparameter verfügen. Eine Schnittstelle oder ein Delegattyp kann sowohl kovariante, als auch kontravariante Typparameter haben.

In Visual Basic und C# müssen die Regeln zum Verwenden von kovarianten und kontravarianten Typparametern eingehalten werden, und es ist nicht möglich, Kovarianz- und Kontravarianzkennzeichnungen zu den Typparametern anderer Typen als Schnittstellen- und Delegattypen hinzuzufügen. Der MSIL-Assembler führt keine solchen Überprüfungen aus, es wird aber eine TypeLoadException ausgelöst, wenn Sie versuchen, einen Typ zu laden, der gegen die Regeln verstößt.

Weitere Informationen und einen Beispielcode finden Sie unter Varianz in generischen Schnittstellen (C# und Visual Basic).

Zurück nach oben

Anzeigen: