Différence entre Covariance et contre-variance

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

134
demandé sur jrharshath 2010-02-02 17:07:33

5 réponses

la question Est "Quelle est la différence entre covariance et contre-variance?"

la Covariance et la contravariance sont des propriétés de une cartographie de la fonction qui associe un membre d'un jeu avec un autre . Plus précisément, une cartographie peut être covariante ou contravariante par rapport à une relation 1519240920 "sur cet ensemble.

considérer les deux sous-ensembles suivants de l'ensemble de tous les types C#. Premier:

{ Animal, 
  Tiger, 
  Fruit, 
  Banana }.

et deuxièmement, cet ensemble clairement lié:

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

il y a une opération mapping du premier jeu au second jeu. C'est-à-dire, pour chaque T dans le premier ensemble, le type correspondant dans le deuxième ensemble est IEnumerable<T> . Ou, en résumé, la cartographie est T → IE<T> . Notez que ceci est une "mince flèche".

Avec moi jusqu'à présent?

considérons maintenant une relation . Il y a une "relation de compatibilité d'affectation entre paires de types dans le premier ensemble. Une valeur de type Tiger peut être assignée à une variable de type Animal , de sorte que ces types sont dits"compatibles affectation". Écrivons "une valeur de type X peut être assignée à une variable de type Y "dans une forme plus courte: X ⇒ Y . Remarquez que c'est une "grosse flèche".

donc dans notre premier sous-ensemble, voici toutes les relations de compatibilité d'affectation:

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>

Avis que la cartographie T → IE<T> préserve l'existence et de la direction de la compatibilité d'affectation . C'est-à-dire, si X ⇒ Y , alors il est aussi vrai que IE<X> ⇒ IE<Y> .

si nous avons deux choses de chaque côté d'une flèche grosse, alors nous pouvons remplacer les deux côtés par quelque chose du côté droit d'une flèche fine correspondante.

une cartographie qui possède cette propriété par rapport à une relation particulière est appelée"cartographie covariante". Cela devrait avoir du sens: une séquence de tigres peut être utilisée lorsqu'une séquence D'animaux est nécessaire, mais l'inverse n'est pas vrai. Une séquence d'animaux ne peut pas nécessairement être utilisée lorsqu'une séquence de tigres est nécessaire.

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

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

nous avons maintenant le mapping du premier jeu au troisième jeu T → IC<T> .

In 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-à-dire, la cartographie T → IC<T> a préservé la existence mais inversé la direction de compatibilité d'attribution. C'est-à-dire, si X ⇒ Y , puis IC<X> ⇐ IC<Y> .

Une cartographie qui "1519230920 en conserve, mais l'inverse une relation est appelée contravariant cartographie.

Encore une fois, cela devrait être clairement correcte. Un dispositif qui peut comparer deux animaux peut aussi comparer deux tigres, mais un dispositif qui peut comparer deux tigres ne peut pas nécessairement comparez deux animaux.

donc c'est la différence entre covariance et contre-variance dans C# 4. La Covariance préserve la direction de cessibilité. Contravariance inverse it.

245
répondu Eric Lippert 2017-01-10 14:13:17

il est probablement plus facile de donner des exemples - c'est certainement ce dont je me souviens.

Covariance

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

vous pouvez convertir IEnumerable<string> en IEnumerable<object> , ou Func<string> en Func<object> . Les valeurs viennent seulement de ces objets.

cela fonctionne parce que si vous prenez seulement valeurs hors de L'API, et il va retourner quelque chose de spécifique (comme string ), vous pouvez traiter cette valeur retournée comme un type plus général (comme object ).

la Contravariance

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

vous pouvez convertir de IComparer<object> en IComparer<string> , ou Action<object> en Action<string> ; les valeurs vont seulement en ces objet.

cette fois cela fonctionne parce que si L'API attend quelque chose de général (comme object ) vous pouvez lui donner quelque chose de plus spécifique (comme string ).

plus généralement

si vous avez une interface IFoo<T> il peut être covariant dans T (c.-à-d. le déclarer comme IFoo<out T> si T est seulement utilisé dans une position de sortie (par exemple un type de retour) dans le interface. Il peut être contravariant dans T (i.e. IFoo<in T> ) si T est seulement utilisé dans une position d'entrée (par exemple un type de paramètre).

il devient potentiellement confus parce que" position de sortie "n'est pas aussi simple que cela paraît - un paramètre de type Action<T> est encore seulement en utilisant T dans une position de sortie - la contravariance de Action<T> tourne rond, si vous voyez ce que je veux dire. C'est une "sortie" en ce que les valeurs peuvent passer de la la mise en œuvre de la méthode vers le code de l'appelant, tout comme une valeur de retour peut. Habituellement, ce genre de chose ne se présente pas, heureusement:)

98
répondu Jon Skeet 2017-03-06 12:50:48

j'espère que mon post aide à obtenir une langue-vue agnostique 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" cohérence"? L'idée est de concevoir des hiérarchies de type "type-safe" avec des types hautement substituables. La clé pour obtenir cette cohérence est la conformité basée sur un sous-type, si vous travaillez dans un langue. (Nous allons discuter du principe de substitution de Liskov (LSP) à un niveau élevé ici.)

exemples pratiques (pseudo code / invalide en C#):

  • Covariance: supposons que les oiseaux pondent des œufs "systématiquement" avec typage statique: si L'oiseau type pond un œuf, le sous-type de l'oiseau ne pondrait-il pas un sous-type d'œuf? Par exemple: le type canard pose un DuckEgg, puis la consistance est donnée. Pourquoi cela est-il compatible? Parce que dans une telle expression: Egg anEgg = aBird.Lay(); l'oiseau de référence pourrait être légalement remplacé par un oiseau ou par un canard. Nous disons que le type de retour est covariant par rapport au type, dans lequel Lay() est défini. Le surpassement d'un sous-type peut renvoyer un type plus spécialisé. = > "Ils livrent plus."

  • Contravariance: supposons Pianos que les pianistes peuvent jouer "systématiquement" avec la dactylographie statique: si un pianiste joue du Piano, serait-il capable de jouer un GrandPiano? Je ne voudrais pas qu'un virtuose joue un GrandPiano? (Être averti; il y a un twist!) Ce qui est incompatible! Parce que dans une telle expression: aPiano.Play(aPianist); aPiano ne peut pas être légalement substitué par un Piano ou par une instance de GrandPiano! Un GrandPiano ne peut être joué que par un virtuose, les pianistes sont trop généraux! GrandPianos doit être jouable par des types plus généraux, alors la pièce est cohérente. Nous disons que le type de paramètre est contraire au type, dans lequel Play() est défini. Le contournement d'un sous-type peut accepter un type plus généralisé. => "Ils besoin de moins."

retour à C#:

Parce que C# est essentiellement un langage statiquement typé, les" emplacements " de l'interface d'un type qui devrait être co - ou contravariant (par exemple les paramètres et les types de retour), doivent être marqués explicitement pour garantir un usage/développement cohérent de ce type, pour rendre le LSP parfait. Dans les langages dactylographiés dynamiquement la cohérence LSP n'est généralement pas un problème, en d'autres termes, vous pourriez obtenir complètement débarrassez - vous du "markup" CO-et contravariant sur les interfaces .Net et les délégués, si vous n'avez utilisé la dynamique de type que dans vos types. - Mais ce n'est pas la meilleure solution en C# (vous ne devriez pas utiliser dynamic dans les interfaces publiques).

Retour à la théorie:

La conformité décrite (covariant return types / contravariant parameter types) est l'idéal théorique (supporté par les langages Emerald et POOL-1). Certaines langues de l'oop (par exemple Eiffel) ont décidé d'appliquer une autre type de cohérence, esp. aussi les types de paramètres covariants, parce qu'il décrit mieux la réalité que l'idéal théorique. Dans les langages statiquement dactylographiés, la cohérence désirée doit souvent être obtenue par l'application de modèles de conception tels que "Expédition double" et "visiteur". D'autres langages fournissent ce qu'on appelle des "dispatches multiples" ou des méthodes multi (il s'agit essentiellement de sélectionner des surcharges de fonction à run time , p.ex. avec CLOS) ou obtiennent l'effet désiré en utilisant dynamique taper.

13
répondu Nico 2015-12-11 11:28:50

si vous voulez attribuer une méthode à un délégué, la méthode signature doit correspondre exactement à la signature du délégué. Cela étant dit, la covariance et la contravariance laissent une certaine marge de manœuvre pour faire correspondre la signature des méthodes à celle des délégués.

vous pouvez vous référer à cet article pour comprendre covariance, contravariance, et les différences entre eux .

2
répondu Ranganatha 2016-06-30 16:14:51

le représentant du convertisseur m'aide à comprendre la différence.

delegate TOutput Converter<in TInput, out TOutput>(TInput input);

TOutput représente covariance où une méthode retourne un type plus spécifique .

TInput représente contravariance lorsqu'une méthode est approuvée Type moins spécifique .

public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }

public static Poodle ConvertDogToPoodle(Dog dog)
{
    return new Poodle() { Name = dog.Name };
}

List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();
1
répondu woggles 2017-10-06 02:51:08