Comprendre les interfaces covariantes et contravariantes en C #

Je les ai trouvés dans un manuel que je lis sur C #, mais j’ai du mal à les comprendre, probablement à cause du manque de contexte.

Existe-t-il une bonne explication concise de ce qu’elles sont et de ce qu’elles sont utiles?

Modifier pour clarification:

Interface covariante:

interface IBibble . . 

Interface contravariante:

 interface IBibble . . 

Avec , vous pouvez traiter la référence de l’interface comme une référence ascendante dans la hiérarchie.

Avec , vous pouvez traiter la référence de l’interface comme une référence descendante dans la hiérarchie.

Permettez-moi d’essayer de l’expliquer en termes plus anglais.

Supposons que vous récupériez une liste d’animaux de votre zoo et que vous avez l’intention de les traiter. Tous les animaux (dans votre zoo) ont un nom et un identifiant unique. Certains animaux sont des mammifères, certains sont des reptiles, certains sont des amphibiens, certains sont des poissons, etc., mais ils sont tous des animaux.

Donc, avec votre liste d’animaux (qui contient des animaux de différents types), vous pouvez dire que tous les animaux ont un nom, alors évidemment, il serait prudent d’obtenir le nom de tous les animaux.

Cependant, que se passe-t-il si vous avez une liste de poissons seulement, mais que vous devez les traiter comme des animaux, cela fonctionne-t-il? Intuitivement, cela devrait fonctionner, mais en C # 3.0 et avant, ce morceau de code ne comstackra pas:

 IEnumerable animals = GetFishes(); // returns IEnumerable 

La raison en est que le compilateur ne “sait” pas ce que vous avez l’intention de faire ou que vous pouvez faire avec la collection d’animaux après l’avoir récupérée. Pour autant qu’il le sache, il pourrait y avoir un moyen, via IEnumerable de replacer un object dans la liste, ce qui pourrait vous permettre de placer un animal qui n’est pas un poisson dans une collection censée contenir uniquement poisson.

En d’autres termes, le compilateur ne peut pas garantir que cela n’est pas autorisé:

 animals.Add(new Mammal("Zebra")); 

Le compilateur refuse donc tout simplement de comstackr votre code. C’est la covariance.

Regardons la contravariance.

Comme notre zoo peut manipuler tous les animaux, il peut certainement manipuler du poisson, alors essayons d’append du poisson à notre zoo.

En C # 3.0 et avant, cela ne comstack pas:

 List fishes = GetAccessToFishes(); // for some reason, returns List fishes.Add(new Fish("Guppy")); 

Ici, le compilateur pourrait autoriser ce morceau de code, même si la méthode retourne List simplement parce que tous les poissons sont des animaux, donc si nous venons de changer les types à ceci:

 List fishes = GetAccessToFishes(); fishes.Add(new Fish("Guppy")); 

Ensuite, cela fonctionnerait, mais le compilateur ne peut pas déterminer que vous n’essayez pas de faire cela:

 List fishes = GetAccessToFishes(); // for some reason, returns List Fish firstFist = fishes[0]; 

Comme la liste est en fait une liste d’animaux, cela n’est pas autorisé.

Donc, la contre-variance est la façon dont vous traitez les références aux objects et ce que vous êtes autorisé à faire avec elles.

Les mots-clés entrants et sortants dans C # 4.0 marquent spécifiquement l’interface comme l’un ou l’autre. Avec in , vous êtes autorisé à placer le type générique (généralement T) dans les positions d’ entrée , ce qui signifie des arguments de méthode et des propriétés en écriture seule.

Sans, vous êtes autorisé à placer le type générique dans les positions de sortie , qui sont les valeurs de retour de méthode, les propriétés en lecture seule et les parameters de méthode de sortie.

Cela vous permettra de faire ce que vous voulez faire avec le code:

 IEnumerable animals = GetFishes(); // returns IEnumerable // since we can only get animals *out* of the collection, every fish is an animal // so this is safe 

List a à la fois des entrées et des sorties sur T, ce n’est donc ni une variante ni une contre-variante, mais une interface qui vous a permis d’append des objects, comme ceci:

 interface IWriteOnlyList { void Add(T value); } 

vous permettrait de le faire:

 IWriteOnlyList fishes = GetWriteAccessToAnimals(); // still returns IWriteOnlyList fishes.Add(new Fish("Guppy")); <-- this is now safe 

Voici quelques vidéos montrant les concepts:

  • Covariance et Contravariance - VS2010 C # Partie 1 sur 3
  • Covariance et Contravariance - VS2010 C # Partie 2 sur 3
  • Covariance et Contravariance - VS2010 C # Partie 3 sur 3

Voici un exemple:

 namespace SO2719954 { class Base { } class Descendant : Base { } interface IBibbleOut { } interface IBibbleIn { } class Program { static void Main(ssortingng[] args) { // We can do this since every Descendant is also a Base // and there is no chance we can put Base objects into // the returned object, since T is "out" // We can not, however, put Base objects into b, since all // Base objects might not be Descendant. IBibbleOut b = GetOutDescendant(); // We can do this since every Descendant is also a Base // and we can now put Descendant objects into Base // We can not, however, resortingeve Descendant objects out // of d, since all Base objects might not be Descendant IBibbleIn d = GetInBase(); } static IBibbleOut GetOutDescendant() { return null; } static IBibbleIn GetInBase() { return null; } } } 

Sans ces marques, les éléments suivants pourraient être compilés:

 public List GetDescendants() ... List bases = GetDescendants(); bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant 

ou ca:

 public List GetBases() ... List descendants = GetBases(); <-- uh-oh, we try to treat all Bases as Descendants 

Ce post est le meilleur que j’ai lu sur le sujet

En bref, la covariance / contravariance / invariance traite de la conversion de type automatique (de base à dérivée et vice-versa). Ces transtypages ne sont possibles que si certaines garanties sont respectées en termes d’actions de lecture / écriture effectuées sur les objects convertis. Lisez l’article pour plus de détails.