144 votes

Différence entre covariance et contre-variance

J'ai du mal à comprendre la différence entre covariance et contravariance.

260voto

Eric Lippert Points 300275

La question est "quelle est la différence entre la covariance et la contravariance?"

La Covariance et la contravariance sont les propriétés d' une fonction de mappage qui associe un membre d'un jeu à un autre. Plus précisément, un mappage peut être covariant et contravariant à l'égard d'un rapport sur ce jeu.

Considérons les deux sous-ensembles de l'ensemble de tous les C# types. D'abord:

{ Animal, 
  Tiger, 
  Fruit, 
  Banana }.

Et la seconde, c'est clairement lié ensemble:

{ IEnumerable<Animal>, 
  IEnumerable<Tiger>, 
  IEnumerable<Fruit>, 
  IEnumerable<Banana> }

Il y a une cartographie de l'opération à partir du premier jeu de la deuxième série. C'est, pour chaque T dans le premier set, le correspondant de type dans le deuxième set, est - IEnumerable<T>. Ou, dans sa forme courte, la cartographie est - T → IE<T>.

Avec moi jusqu'à présent?

Maintenant, nous allons envisager une relation. Il y a une compatibilité d'affectation de la relation entre les paires de types dans le premier set. Une valeur de type Tiger peut être affectée à une variable de type Animal, de sorte que ces types sont dit "affectation compatible". Nous allons écrire "une valeur de type X peut être affectée à une variable de type Y" dans une forme abrégée: X ⇒ Y. Donc, dans notre premier sous-ensemble, voici l'assignation relations de compatibilité:

Tiger  ⇒ Tiger
Tiger  ⇒ Animal
Animal ⇒ Animal
Banana ⇒ Banana
Banana ⇒ Fruit
Fruit  ⇒ Fruit

En C# 4, qui prend en charge covariante d'attribution de la compatibilité de certaines interfaces, il y a une assignation de la compatibilité de la relation entre les paires de types dans le deuxième set:

IE<Tiger>  ⇒ IE<Tiger>
IE<Tiger>  ⇒ IE<Animal>
IE<Animal> ⇒ IE<Animal>
IE<Banana> ⇒ IE<Banana>
IE<Banana> ⇒ IE<Fruit>
IE<Fruit>  ⇒ IE<Fruit>

Notez que le mappage T → IE<T> préserve l'existence et de la direction de la compatibilité d'affectation. C'est, si X ⇒ Y, puis il est également vrai qu' IE<X> ⇒ IE<Y>.

Une cartographie qui a cette propriété à l'égard d'un rapport particulier est appelé une "covariant de la cartographie". Cela doit faire sens: une séquence de Tigres peuvent être utilisés où une séquence d'Animaux est nécessaire, mais l'inverse n'est pas vrai. Une séquence d'animaux ne peuvent pas nécessairement être utilisés où une séquence de Tigres est nécessaire.

C'est la covariance. Considérons maintenant ce sous-ensemble de l'ensemble de tous les types:

{ IComparable<Tiger>, 
  IComparable<Animal>, 
  IComparable<Fruit>, 
  IComparable<Banana> }

maintenant, nous avons la cartographie à partir du premier jeu du troisième set T → IC<T>.

En C# 4:

IC<Tiger>  ⇒ IC<Tiger>
IC<Animal> ⇒ IC<Tiger>     Backwards!
IC<Animal> ⇒ IC<Animal>
IC<Banana> ⇒ IC<Banana>
IC<Fruit>  ⇒ IC<Banana>     Backwards!
IC<Fruit>  ⇒ IC<Fruit>

C'est le mapping T → IC<T> a préservé l'existence, mais inversé le sens de la compatibilité d'affectation. C'est, si X ⇒ Y, alors IC<X> ⇐ IC<Y>.

Une cartographie qui préserve mais renverse une relation est appelée contravariant de la cartographie.

Encore une fois, cela devrait être clairement correcte. Un dispositif qui permet de comparer deux Animaux peut aussi comparer deux Tigres, mais un appareil qui permet de comparer deux Tigres ne peut pas vraiment comparer les deux Animaux.

Donc, c'est la différence entre la covariance et la contravariance en C# 4. La Covariance conserve la direction de cessibilité. La Contravariance inverse .

107voto

Jon Skeet Points 692016

Il est probablement plus facile de donner des exemples - c'est certainement la façon dont je me souviens.

La Covariance

Des exemples canoniques: IEnumerable<out T>, Func<out T>

Vous pouvez convertir IEnumerable<string> de IEnumerable<object>ou Func<string> de Func<object>. Les valeurs ne viennent de ces objets.

Il fonctionne parce que si vous êtes seulement en prenant les valeurs de l'API, et il va revenir à quelque chose de spécifique (comme string), vous pouvez considérer que la valeur retournée comme un type plus général (comme object).

La Contravariance

Des exemples canoniques: IComparer<in T>, Action<in T>

Vous pouvez convertir IComparer<object> de IComparer<string>ou Action<object> de Action<string>; les valeurs ne vont dans ces objets.

Cette fois, il fonctionne parce que si l'API s'attend à quelque chose de plus général (comme object), vous pouvez lui donner quelque chose de plus précis (comme string).

Plus généralement

Si vous disposez d'une interface IFoo<T> il peut être covariants en T (c'est à dire déclarer comme IFoo<out T> si T n'est utilisé que dans une position de sortie (par exemple, un type de retour) au sein de l'interface. Il peut être contravariant en T (c - IFoo<in T>) si T n'est utilisé que dans une position d'entrée (par exemple, un type de paramètre).

Il devient potentiellement source de confusion parce que "la position de sortie" n'est pas aussi simple qu'il y paraît, un paramètre de type Action<T> est encore qu'à l'aide d' T dans une position de sortie - la contravariance d' Action<T> tourne autour, si vous voyez ce que je veux dire. Habituellement, ce genre de chose n'est pas, heureusement :)

15voto

Nico Points 667

J'espère que mon post permet d'obtenir un indépendant de la langue de vue du sujet.

Pour nos formations internes, j'ai travaillé avec le merveilleux livre "Smalltalk, Objets et Design (Chamond Liu)" et j'ai reformulé exemples suivants.

Que signifie la "cohérence"? L'idée est de concevoir type-safe type de hiérarchies hautement substituables types. La clé pour obtenir cette cohérence est sous-type de mise en conformité, si vous travaillez dans un langage statiquement typé. (Nous aborderons le Principe de Substitution de Liskov (LSP) à un niveau élevé ici.)

Exemples pratiques:

  • La Covariance: supposons Oiseaux qui pondent des Œufs "cohérente" avec le typage statique: Si le type de l'Oiseau pond un Œuf, ne serait pas de l'Oiseau sous-type de jeter un sous-type de l'Oeuf? E. g. le type de Canard pond un DuckEgg, alors la cohérence est donnée. Pourquoi cela est-il compatible? Parce que dans cette expression:Egg anEgg = aBird.Lay();la référence aBird peuvent légalement être substitué par un Oiseau ou un Canard instance. Nous disons que le type de retour est covariante du type, dans lequel Laïques() est définie. Un sous-type de remplacer peut renvoyer à un plus spécialisé. = > ", Ils le leur offrir."

  • La Contravariance: supposons Pianos que les Pianistes peuvent jouer "cohérente" avec le typage statique: Si un Pianiste joue du Piano, serait-elle capable de jouer un GrandPiano? Ne serait pas plutôt un Virtuose de jouer un GrandPiano? (Être averti; il y a un twist!) C'est incohérent! Parce que dans cette expression: aPiano.Play(aPianist); aPiano ne pouvait pas être légalement remplacé par un Piano ou par un GrandPiano exemple! Un GrandPiano peut seulement être joué par un Virtuose, les Pianistes sont trop générale! GrandPianos doivent être lisibles par le plus général, les types, alors le jeu est cohérent. Nous dire le type de paramètre est contravariant le type, dans lequel Jouer() est définie. Un sous-type de remplacer peut accepter une plus généralisée de type. = > ", Ils nécessitent moins."

En C#:
Parce que le C# est essentiellement un langage statiquement typé, les "lieux" d'un type de l'interface qui doit être co - contravariant (par exemple, les paramètres et les types de retour), doit être explicitement marqué pour garantir une utilisation cohérente/développement de ce type, de faire de la LSP, beau travail. Dans typées dynamiquement langues LSP cohérence est généralement pas un problème, en d'autres termes, vous pourriez vous débarrasser complètement de co - et contravariant "balisage".Net des interfaces et des délégués, si vous n'utilisez que le type de dynamique dans votre types. - Mais ce n'est pas la meilleure solution en C# (vous ne devriez pas utiliser dynamique dans les interfaces publiques).

De retour à la théorie:
La description de la conformité (covariant types de retour/contravariant types de paramètres) est l'idéal théorique (pris en charge par la langue, d'Émeraude et de la PISCINE-1). Certains langages oop (par exemple Eiffel) a décidé d'appliquer un autre type de cohérence, esp. aussi covariante des types de paramètres, car il décrit mieux la réalité de l'idéal théorique. Dans les langages statiquement typés la consistance désirée doit souvent être atteint par l'application de modèles de conception comme "double expédition" et "visiteur". D'autres langues fournir de soi-disant "la répartition multiple" ou multi-méthodes (ce qui est essentiellement fonction de sélection de surcharges au moment de l'exécution, par ex. avec des CLOS) ou obtenir l'effet désiré en utilisant typage dynamique.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X