Was ist eigentlich Kontravarianz für Generics?

Was ist eigentlich Kontravarianz für Generics?

Von Jan Suchotzki

In Kovarianz und Felder hast du gesehen, dass das Konzept der Kovarianz bei Feldern etwas kompliziert ist, aber die praktische Umsetzung sehr einfach. Nun möchte ich dir zeigen wie du generische Interfaces mit kontravarianten Typen verwendest.

Als Beispiel habe ich mal IComparable<T> genommen. Dies ist eines von vielen Interfaces, welches sich mit dem Vergleich von Objekten beschäftigt. Mit IComparable<T> kannst du deiner Klasse ein Standardverhalten für das Vergleichen von Objekten deiner Klasse mitgeben. Bei einer Klasse LernMoment könntest du beispielsweise sagen, dass eine Instanz von LernMoment größer als eine andere ist, wenn die Eigenschaft ErschienenAm neuer ist.

Was aber hat das nun mit Kontravarianz zutun? Schau dir dazu mal folgende Aussage für den Typparameter in T der Definition public interface IComparable<in T> an:

Dieser Typparameter ist Contravariant. Das heißt, Sie können entweder den angegebenen Typ oder einen weniger abgeleiteten Typ verwenden. –MSDN

Bei Kontravarianz kannst du anstelle vom Typ T eine Klasse verwenden von der T erbt. Um das etwas genauer anzuschauen, nehmen wir folgende Klassenhierarchie:

class LernMoment : IComparable<LernMoment> { }
class LernMomentCSharp : LernMoment, IComparable<LernMomentCSharp> { }

Das Zitat der MSDN will dir nun sagen, dass du folgendes machen kannst:

IComparable<LernMomentCSharp> derVergleich = new LernMoment();

Die Kontravarianz erlaubt dir, dass du, auch wenn der Typ IComparable<LernMomentCSharp> gefordert ist, einen weniger abgeleiteten Typen verwenden kannst. In diesem Beispiel ist das IComparable<LernMoment>. Beide Klassen können CompareTo, die einzige Methode von IComparable<T>, nun nach ihren jeweiligen Bedürfnissen realisieren.

So wäre es möglich, dass LernMoment den Vergleich basierend auf der Eigenschaft ErschienenAm macht, während LernMomentCSharp vielleicht eher die Eigenschaft Name verwendet. Das Interface und die CompareTo Methode verwendest du übrigens selten direkt. Insbesondere Sortieralgorithmen wie List<T>.Sort verwenden sie.

Im Beispielquelltext zeige ich dir mit welchem einfachen Trick du List<LernMomentCSharp>.Sort sagen kannst, welche Variante der CompareTo Methode verwendet werden soll.

Jetzt erstmal viel Spaß mit dem Vergleich verschiedener Vererbungsstufen

Jan

PS: Eine wirklich sinnvolle Verwendung von Kontravarianz an generischen Interfaces bietet übrigens IComparer<T>. Das ist aber Stoff für einen weiteren LernMoment.

Merke

  • Kovarianz und Kontravarianz gibt es auch für Delegates und Interfaces mit generischen Typen.
  • Wenn ein generischer Typ in einem Interface kovariant ist, heißt dass, das du den angegebenen Typen oder einen von ihm abgeleiteten verwenden kannst.
  • Wenn ein generischer Typ in einem Interface kontravariant ist, heißt dass, das du den angegebenen Typen oder einen Typen von dem er erbt, verwenden kannst.
  • Mit IComparable<T> kannst du das Standardverhalten für Vergleiche (insbesondere für Sortieralgorithmen) einer Klasse definieren.
  • Wenn du zusätzliche Vergleiche brauchst, dann kannst du IComparer<T> verwenden.

Lernquiz

Verwende folgende Fragen, um das Gelernte von heute zu festigen:

  • Was bedeutet es, wenn an einem generischen Interface steht, dass der Typparameter kontravariant ist?
  • Was macht IComparable?
  • Was ist der Unterschied zwischen Kovarianz und Kontravarianz?

Am besten schaust du dir morgen und dann nochmal in ein paar Tagen die vorherigen Fragen an und beantwortest sie, ohne den Text vorher gelesen zu haben.

Weitere Informationen

  • Den kompletten Quelltext zum heutigen Lernmoment findest du hier.
  • Einen groben Überblick inklusive weiterer Links zum Thema Kovarianz und Kontravarianz findest du hier.
  • Eine sehr detailierte und mathematische Erklärung gibt dieser Artikel.
  • Eine weitere sehr gute Einführung in das Thema findest du auf MSDN