42 votes

Exemples simples de covariance et de contravariance

Est-ce que quelqu'un pourrait me fournir des exemples simples en C# de covariabilité, contravariabilité, invariabilité et contra-invariabilité (si une telle chose existe).

Tous les exemples que j'ai vus jusqu'à présent se limitaient simplement à caster un objet en System.Object.

0 votes

Quelqu'un a donné la distribution d'un objet à System.Object comme exemple de covariance? C'est complètement faux.

0 votes

J'ai interprété cela comme le passage d'un genre d'objet (avec un type plus spécifique, plus dérivé) à la place de System.Object, et non pas nécessairement la conversion de object en System.Object, ce qui serait inutile.

0 votes

Variance ne doit pas être confondu avec casting, ils ne sont pas la même chose. Voir: Différence entre covariance et upcasting.

89voto

Eric Lippert Points 300275

Est-ce que quelqu'un pourrait me fournir des exemples simples en C# de covariance, contravariance, invariance et contrainvariance (si cela existe).

Je n'ai aucune idée de ce que "contrainvariance" signifie. Le reste est facile.

Voici un exemple de covariance:

void NourrirLesAnimaux(IEnumerable animaux) 
{ 
    foreach(Animal animal in animaux)
        animal.Nourrir();
}
...
List girafes = ...;
NourrirLesAnimaux(girafes);

L'interface IEnumerable est covariante. Le fait que Girafe est convertible en Animal implique que IEnumerable est convertible en IEnumerable. Comme List implémente IEnumerable, ce code fonctionne en C# 4; il aurait échoué en C# 3 car la covariance sur IEnumerable ne fonctionnait pas en C# 3.

Cela devrait avoir du sens. Une séquence de girafes peut être traitée comme une séquence d'animaux.

Voici un exemple de contravariance:

void FaireQuelqueChoseAUneGrenouille(Action action, Grenouille grenouille)
{
    action(grenouille);
}
...
Action nourrir = animal=>{animal.Nourrir();}
FaireQuelqueChoseAUneGrenouille(nourrir, new Grenouille());

Le délégué Action est contravariant. Le fait que Grenouille est convertible en Animal implique que Action est convertible en Action. Remarquez comment cette relation est en sens inverse de la covariante; c'est pourquoi c'est "contra" variant. En raison de la convertibilité, ce code fonctionne; il aurait échoué en C# 3.

Cela devrait avoir du sens. L'action peut prendre n'importe quel Animal; nous avons besoin d'une action qui peut prendre n'importe quel Grenouille, et une action qui peut prendre n'importe quel Animal peut sûrement aussi prendre n'importe quel Grenouille.

Un exemple d'invariance:

void LireEtEcrire(IList mammiferes)
{
    Mammifere mammifere = mammiferes[0];
    mammiferes[0] = new Tigre();
}

Pouvons-nous passer une IList à cette fonction? Non, car quelqu'un va écrire un Tigre dedans, et un Tigre ne peut pas être dans une liste de girafes. Pouvons-nous passer une IList à cette fonction? Non, car nous allons lire un Mammifère à partir de celle-ci, et une liste d'Animaux peut contenir une Grenouille. IList est invariante. Il ne peut être utilisé que tel qu'il est réellement.

Pour quelques réflexions supplémentaires sur la conception de cette fonctionnalité, consultez ma série d'articles sur comment nous l'avons conçue et construite.

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

2 votes

Il pourrait être utile de souligner dans votre dernier exemple, bien sûr, que vous pourriez lancer un Girafe[] à un Mammifère[] et passer cela, ce qui entraînerait une erreur d'exécution.

3 votes

@Dan: Bon point. C# prend en charge la covariance "cassée" des tableaux, où certaines conversions covariantes sont autorisées même si elles peuvent causer des plantages à l'exécution.

0 votes

@Eric: Ouais, je comprends définitivement la raison (je pense) : souvent les développeurs veulent un "sous-ensemble" de l'interface d'un T[] (ou IList), pour accéder aux éléments par indice sans l'intention d'écrire dans la collection. C'est pourquoi je crois assez fortement qu'il devrait y avoir quelque chose comme une interface covariante IArray avec un getter indexé dans la BCL. Cela permettrait à une List d'agir comme une IList lorsqu'elle est uniquement utilisée pour un accès indexé et non pour ses caractéristiques mutables (c'est-à-dire dans le même cas où un Giraffe[] peut agir en toute sécurité comme un Mammal[]).

4voto

CodesInChaos Points 60274

L'invariance (dans ce contexte) est l'absence à la fois de co- et de contravariance. Ainsi, le mot contra-invariance n'a aucun sens. Tout paramètre de type qui n'est pas étiqueté comme étant in ou out est invariant. Cela signifie que ce paramètre de type peut à la fois être consommé et retourné.

Un bon exemple de co-variance est IEnumerable car un IEnumerable peut être substitué à un IEnumerable. Ou Func qui retourne des valeurs de type T.
Par exemple, un IEnumerable peut être converti en IEnumerable car tout Chien est un animal.

Pour la contravariance, vous pouvez utiliser n'importe quelle interface de consommation ou déléguée. IComparer ou Action me viennent à l'esprit. Ceux-ci ne retournent jamais une variable de type T, ils la reçoivent uniquement. Partout où vous vous attendez à recevoir un Base, vous pouvez passer un Dérivé.

Penser à eux comme des paramètres de type uniquement en entrée ou en sortie rend les choses plus faciles à comprendre à mon avis.

Et le mot invariants n'est généralement pas utilisé en association avec la variance de type, mais dans le contexte d'invariants de classe ou de méthode, et représente une propriété conservée. Voir ce fil de discussion stackoverflow où les différences entre invariants et invariance sont discutées.

0 votes

Est-ce exact - "wherever you expect to receive a base, you can pass in a derived"?

2voto

Mark H Points 9127

Si vous considérez l'utilisation régulière des generics, vous utilisez régulièrement une interface pour manipuler un objet, mais l'objet est une instance d'une classe - vous ne pouvez pas instancier l'interface. Utilisez une simple liste de chaînes comme exemple.

IList strlist = new List();

Je suis sûr que vous êtes conscient des avantages d'utiliser un IList<> plutôt que d'utiliser directement un List<>. Cela permet l'inversion de contrôle, et vous pourriez décider de ne plus utiliser un List<>, mais plutôt un LinkedList<>. Le code ci-dessus fonctionne bien car le type générique de l'interface et de la classe est le même : string.

Cela peut devenir un peu plus compliqué si vous voulez créer une liste de liste de chaînes. Considérez cet exemple :

IList> strlists = new List>();

Cela ne compilera clairement pas, car les arguments de types génériques IList et List ne sont pas les mêmes. Même si vous aviez déclaré la liste extérieure comme une classe régulière, comme List>, cela ne compilerait pas - les arguments de type ne correspondent pas.

C'est là que la covariance peut être utile. La covariance vous permet d'utiliser un type plus dérivé comme argument de type dans cette expression. Si IList<> était déclaré comme covariant, cela compilerait simplement et résoudrait le problème. Malheureusement, IList<> n'est pas covariant, mais l'une des interfaces qu'elle étend l'est :

IEnumerable> strlists = new List>();

Ce code compile maintenant, les arguments de type sont les mêmes qu'auparavant.

1voto

Nekresh Points 1900

Jettes un coup d'œil à cet article msdn

0voto

Kurru Points 6746

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