Kovarianz und Kontravarianz

22. Juli 2015

So hochtrabend die Bezeichnung Kovarianz und Kontravarianz klingt, so simpel und alt, sehr alt, ist die Idee dahinter.

Spezialisierung und Generalisierung sind Grundkonzepte der Objektorientierten Programmierung. Wie in Zeile 8 aufgezeigt, konnte man schon immer eine spezialisierte Instanz einer Klasse einer generalisierten Variable zuweisen.

   1: public class Control {}

   2: public class ItemContainer : Control{}

   3: public class Item : Control{}

   4:  

   5: public static void Main()

   6: {

   7:     Item i = new Item();

   8:     Control c = Do(i);

   9: }

  10:  

  11: public static ItemContainer Do(Control arg)

  12: {

  13:     ...

  14:     return new ItemContainer();

  15: }

Delegates gab es zwar schon vor LINQ, jedoch erlebten sie damit einen Hype. Folgendes konnte man schon immer tun:

   1: Item i = new Item();

   2: Func<Control, ItemContainer> do = arg => Do(arg);

   3: Control c = do(i);

Im Zusammenhang mit LINQ werden Delegates aber sehr oft selbst als Parameter für Methodenaufrufe verwendet. Nun kommen Kovarianz und Kontravarianz zum Einsatz:

   1: Item i = new Item();

   2: Func<Control, ItemContainer> do = arg => Do(arg);

   3: Func<Control, Control> doGeneral = do;

   4:  

   5: Control c = doGeneral(i);

Während folgende Zuweisungen den gewünschten Effekt haben,

   1: string[] sItems = new string[]{ "abc", "123"};

   2: object[] oItems = sItems;

   3:  

   4: IEnumerable<string> sEnumerable = sItems;

   5: IEnumerable<object> oEnumerable = sEnumerable

führt Zeile 2 zum Fehler. Verständlicherweise zum Fehler, denn man könnte oItems.Add mit beliebigen Argumenten aufrufen.

   1: IList<string> sItems = new string[]{ "abc", "123"};

   2: IList<object> oItems = sItems;

Trotzdem würde ich mir wünschen, dass Microsoft nicht nur Delegates und IEnumerable<> mit dem Feature versieht. Sondern beispielsweise auch:

   1: public interface IControl { }

   2:  

   3: public interface IItemContainer

   4: {

   5:     IEnumerable<IControl> Controls { get; }

   6: }

   7:  

   8: public class Control : IControl {}

   9:  

  10: public class ItemContainer : Control, IItemContainer

  11: {

  12:     public IList<Control> Controls { get; }

  13: }

Der Code  wird witzigerweise erst lauffähig mit folgender Erweiterung:

   1: public class ItemContainer : Control, IItemContainer

   2: {

   3:     public IList<Control> Controls { get; set; }

   4:  

   5:     IEnumerable<IControl> IItemContainer.Controls

   6:     {

   7:         get { return this.Controls; }

   8:     }

   9: }

Für mich leider unverständlich, denn IList implementiert IEnumerable und dort ist eine Generalisierung erlaubt.

Während die Grundkonzepte der OOP für LINQ auf Delegates und IEnumerable eingeführt wurden, ist mir unverständlich warum sie nicht auch für das obige Beispiel gelten. Mir ist klar dass IEnumerable nicht die Sicht auf die Liste ändert sondern nur durch Enumerator.Current die Sicht auf ein Item, aber dies tut IItemContainer auch. Man muss bei der expliziten Implementierung nicht mal casten!