J'ai un peu de mal à comprendre comment j'utiliserais la covariance et la contravariance dans le monde réel.
Jusqu'à présent, les seuls exemples que j'ai vus sont le même ancien exemple de tableau.
object[] objectArray = new string[] { "string 1", "string 2" };
Ce serait bien de voir un exemple qui me permettrait de l'utiliser lors de mon développement si je pouvais le voir être utilisé ailleurs.
c#
c#-4.0
covariance
Le rasoir
la source
la source
Réponses:
Disons que vous avez une classe Personne et une classe qui en dérive, Maître. Vous avez des opérations qui prennent un
IEnumerable<Person>
comme argument. Dans votre classe School, vous avez une méthode qui renvoie un fichierIEnumerable<Teacher>
. La covariance vous permet d'utiliser directement ce résultat pour les méthodes qui prennent unIEnumerable<Person>
, en remplaçant un type plus dérivé par un type moins dérivé (plus générique). Contravariance, contre-intuitivement, vous permet d'utiliser un type plus générique, où un type plus dérivé est spécifié.Voir aussi Covariance et Contravariance dans les génériques sur MSDN .
Classes :
Utilisation :
la source
Par souci d'exhaustivité…
la source
void feed(IGobbler<Donkey> dg)
. Si vous preniez plutôt un IGobbler <Quadruped> comme paramètre, vous ne pourriez pas passer un dragon qui ne mange que des ânes.Voici ce que j'ai mis en place pour m'aider à comprendre la différence
tldr
la source
Contravariance
exemple) quandFruit
est le parent deApple
?Les mots clés in et out contrôlent les règles de conversion du compilateur pour les interfaces et les délégués avec des paramètres génériques:
la source
Voici un exemple simple utilisant une hiérarchie d'héritage.
Compte tenu de la hiérarchie de classes simple:
Et en code:
Invariance (c.-à-d. Paramètres de type générique * non * décorés avec des mots clés
in
ouout
)Apparemment, une méthode comme celle-ci
... devrait accepter une collection hétérogène: (ce qu'elle fait)
Cependant, le passage d'une collection d'un type plus dérivé échoue!
Pourquoi? Parce que le paramètre générique
IList<LifeForm>
n'est pas covariant -IList<T>
est invariant, doncIList<LifeForm>
n'accepte que les collections (qui implémentent IList) où le type paramétréT
doit êtreLifeForm
.Si l'implémentation de la méthode de
PrintLifeForms
était malveillante (mais a la même signature de méthode), la raison pour laquelle le compilateur empêche le passageList<Giraffe>
devient évidente:Puisque
IList
permet l'ajout ou la suppression d'éléments, toute sous-classe deLifeForm
pourrait donc être ajoutée au paramètrelifeForms
, et violerait le type de toute collection de types dérivés passés à la méthode. (Ici, la méthode malveillante tenterait d'ajouter unZebra
àvar myGiraffes
). Heureusement, le compilateur nous protège de ce danger.Covariance (générique avec type paramétré décoré avec
out
)La covariance est largement utilisée avec les collections immuables (c'est-à-dire lorsque de nouveaux éléments ne peuvent pas être ajoutés ou supprimés d'une collection)
La solution à l'exemple ci-dessus est de s'assurer qu'un type de collection générique covariant est utilisé, par exemple
IEnumerable
(défini commeIEnumerable<out T>
).IEnumerable
n'a pas de méthode à modifier dans la collection, et en raison de laout
covariance, toute collection avec le sous-type deLifeForm
peut maintenant être passée à la méthode:PrintLifeForms
peut maintenant être appelé avecZebras
,Giraffes
et n'importeIEnumerable<>
quelle sous-classe deLifeForm
Contravariance (générique avec type paramétré décoré avec
in
)La contravariance est fréquemment utilisée lorsque les fonctions sont passées en paramètres.
Voici un exemple de fonction, qui prend un
Action<Zebra>
comme paramètre et l'appelle sur une instance connue d'un Zebra:Comme prévu, cela fonctionne très bien:
Intuitivement, cela échouera:
Cependant, cela réussit
et même cela réussit aussi:
Pourquoi? Parce que
Action
est défini commeAction<in T>
, c'est-à-dire qu'il estcontravariant
, ce qui signifie que pourAction<Zebra> myAction
, quimyAction
peut être au plus "a"Action<Zebra>
, mais des superclasses moins dérivées deZebra
sont également acceptables.Bien que cela puisse être non intuitif au début (par exemple, comment
Action<object>
passer un paramètre en tant que paramètre nécessitantAction<Zebra>
?), Si vous décompressez les étapes, vous remarquerez que la fonction appelée (PerformZebraAction
) elle-même est responsable de la transmission des données (dans ce cas, uneZebra
instance ) à la fonction - les données ne proviennent pas du code appelant.En raison de l'approche inversée consistant à utiliser des fonctions d'ordre supérieur de cette manière, au moment où l '
Action
est invoqué, c'est l'Zebra
instance la plus dérivée qui est invoquée contre lazebraAction
fonction (passée en paramètre), bien que la fonction elle-même utilise un type moins dérivé.la source
in
mot - clé utilisé pour la contravariance ?Action<in T>
etFunc<in T, out TResult>
sont contravariants dans le type d'entrée. (Mes exemples utilisent des types existants invariant (List), covariant (IEnumerable) et contravariant (Action, Func))C#
, je ne le saurais pas.Fondamentalement, chaque fois que vous aviez une fonction qui prend un Enumerable d'un type, vous ne pouviez pas passer un Enumerable d'un type dérivé sans le cast explicitement.
Juste pour vous avertir d'un piège:
C'est du code horrible de toute façon, mais il existe et le changement de comportement en C # 4 peut introduire des bogues subtils et difficiles à trouver si vous utilisez une construction comme celle-ci.
la source
Depuis MSDN
la source
Contravariance
Dans le monde réel, vous pouvez toujours utiliser un abri pour animaux au lieu d'un abri pour lapins car chaque fois qu'un refuge pour animaux héberge un lapin, c'est un animal. Cependant, si vous utilisez un refuge pour lapins au lieu d'un refuge pour animaux, son personnel peut être mangé par un tigre.
Dans le code, cela signifie que si vous avez un ,
IShelter<Animal> animals
vous pouvez simplement écrireIShelter<Rabbit> rabbits = animals
si vous promettez et l' utilisationT
dans laIShelter<T>
seule en tant que paramètres de la méthode comme ceci:et remplacer un élément par un élément plus générique, c'est-à-dire réduire la variance ou introduire une contre- variance.
Covariance
Dans le monde réel, vous pouvez toujours utiliser un fournisseur de lapins au lieu d'un fournisseur d'animaux car chaque fois qu'un fournisseur de lapins vous donne un lapin, c'est un animal. Cependant, si vous utilisez un fournisseur d'animaux au lieu d'un fournisseur de lapins, vous pouvez être mangé par un tigre.
Dans le code, cela signifie que si vous avez un,
ISupply<Rabbit> rabbits
vous pouvez simplement écrireISupply<Animal> animals = rabbits
si vous promettez et utiliserT
dans laISupply<T>
méthode only as des valeurs de retour comme suit:et remplacer un élément par un élément plus dérivé, c'est-à-dire augmenter la variance ou introduire la co- variance.
Dans l'ensemble, il ne s'agit que d'une promesse vérifiable lors de la compilation de votre part selon laquelle vous traiteriez un type générique d'une certaine manière pour garder le type en sécurité et ne pas faire manger personne.
Vous voudrez peut-être lire ceci pour revenir en arrière.
la source
contravariance
est intéressant. J'y lis comme indiquant une exigence opérationnelle : que le type plus général doit prendre en charge les cas d'utilisation de tous les types qui en découlent. Donc, dans ce cas, le refuge pour animaux doit être en mesure de prendre en charge l'hébergement de tous les types d'animaux. Dans ce cas, l'ajout d'une nouvelle sous-classe pourrait casser la superclasse! Autrement dit, si nous ajoutons un sous-type de Tyrannosaurus Rex, cela pourrait détruire notre refuge pour animaux existant .Le délégué convertisseur m'aide à visualiser les deux concepts fonctionnant ensemble:
TOutput
représente la covariance où une méthode renvoie un type plus spécifique .TInput
représente une contravariance où une méthode reçoit un type moins spécifique .la source