IEnumerable<T>
est co-variant mais il ne prend pas en charge le type valeur, seulement le type référence. Le code simple ci-dessous est compilé avec succès:
IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;
Mais changer de string
à int
entraînera une erreur de compilation:
IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList;
La raison est expliquée dans MSDN :
La variance s'applique uniquement aux types de référence; si vous spécifiez un type valeur pour un paramètre de type variant, ce paramètre de type est invariant pour le type construit résultant.
J'ai recherché et trouvé que certaines questions mentionnaient que la raison était la boxe entre le type de valeur et le type de référence . Mais cela ne m'éclaircit pas encore beaucoup pourquoi la boxe est la raison?
Quelqu'un pourrait-il donner une explication simple et détaillée pourquoi la covariance et la contravariance ne prennent pas en charge le type de valeur et comment la boxe affecte cela?
la source
Réponses:
Fondamentalement, la variance s'applique lorsque le CLR peut garantir qu'il n'a pas besoin de modifier la représentation des valeurs. Les références se ressemblent toutes - vous pouvez donc utiliser un
IEnumerable<string>
comme unIEnumerable<object>
sans aucun changement de représentation; le code natif lui-même n'a pas du tout besoin de savoir ce que vous faites avec les valeurs, tant que l'infrastructure a garanti qu'elle sera définitivement valide.Pour les types valeur, cela ne fonctionne pas - pour traiter un
IEnumerable<int>
comme unIEnumerable<object>
, le code utilisant la séquence devrait savoir s'il faut effectuer une conversion de boxe ou non.Vous voudrez peut-être lire l' article de blog d' Eric Lippert sur la représentation et l'identité pour en savoir plus sur ce sujet en général.
EDIT: Après avoir relu le blog d'Eric moi-même, c'est au moins autant une question d' identité que de représentation, bien que les deux soient liés. En particulier:
la source
int
n'est pas un sous-type deobject
. Le fait qu'un changement de représentation soit nécessaire n'en est qu'une conséquence.Int32
a deux formes de représentation, "boxed" et "unboxed". Le compilateur doit insérer du code pour convertir d'une forme à l'autre, même si cela est normalement invisible au niveau du code source. En effet, seule la forme "encadrée" est considérée par le système sous-jacent comme un sous-type deobject
, mais le compilateur s'occupe automatiquement de cela chaque fois qu'un type valeur est affecté à une interface compatible ou à quelque chose de typeobject
.Il est peut-être plus facile à comprendre si vous pensez à la représentation sous-jacente (même s'il s'agit vraiment d'un détail d'implémentation). Voici une collection de chaînes:
Vous pouvez considérer le
strings
comme ayant la représentation suivante:C'est une collection de trois éléments, chacun étant une référence à une chaîne. Vous pouvez convertir ceci en une collection d'objets:
En gros, c'est la même représentation sauf que maintenant les références sont des références d'objet:
La représentation est la même. Les références sont simplement traitées différemment; vous ne pouvez plus accéder à la
string.Length
propriété mais vous pouvez toujours appelerobject.GetHashCode()
. Comparez ceci à une collection d'entiers:Pour convertir cela en un,
IEnumerable<object>
les données doivent être converties en encadrant les entiers:Cette conversion nécessite plus qu'un casting.
la source
this
fait référence à une structure dont les champs recouvrent ceux de l'objet de tas qui le stocke, plutôt que de faire référence à l'objet qui les contient. Il n'existe aucun moyen propre pour une instance de type valeur encadrée d'obtenir une référence à l'objet de tas englobant.Je pense que tout commence par la définition du
LSP
(Principe de substitution de Liskov), qui clime:Mais les types valeur, par exemple,
int
ne peuvent pas remplacerobject
inC#
. Prouver est très simple:Cela revient
false
même si nous attribuons la même "référence" à l'objet.la source
int
n'est pas un sous-type deobject
donc le principe ne s'applique pas. Votre «preuve» repose sur une représentation intermédiaireInteger
, qui est un sous-type deobject
et pour laquelle le langage a une conversion implicite (object obj1=myInt;
est en fait étendu àobject obj1=new Integer(myInt)
;).int
n'est pas un sous-type deobject
. De plus, LSP ne s'applique pas carmyInt
,obj1
et seobj2
réfère à trois objets différents: unint
et deux (cachés)Integer
s.int
mot clé C # est un alias pour les BCLSystem.Int32
, qui est en fait un sous-type deobject
(un alias deSystem.Object
). En fait,int
la classe de base de estSystem.ValueType
qui est la classe de baseSystem.Object
. Essayez d' évaluer l'expression suivante et voirtypeof(int).BaseType.BaseType
. La raisonReferenceEquals
renvoie false ici est que leint
est encadré dans deux cases séparées, et l'identité de chaque case est différente pour n'importe quelle autre case. Ainsi, deux opérations de boxe donnent toujours deux objets qui ne sont jamais identiques, quelle que soit la valeur encadrée.System.Int32
ouList<String>.Enumerator
) représente en fait deux types de choses: un type d'emplacement de stockage et un type d'objet de tas (parfois appelé "type de valeur encadré"). Les emplacements de stockage dont les types dériventSystem.ValueType
contiendront les premiers; les objets de tas dont les types font de même contiendront ce dernier. Dans la plupart des langues, une distribution élargie existe du premier au second, et un rétrécissement du second au premier. Notez que tandis que les types de valeur encadrés ont le même descripteur de type que les emplacements de stockage de type valeur, ...Cela se résume à un détail d'implémentation: les types valeur sont implémentés différemment des types référence.
Si vous forcez les types de valeur à être traités comme des types de référence (c.-à-d. Les encadrer, par exemple en y faisant référence via une interface), vous pouvez obtenir la variance.
La façon la plus simple de voir la différence est simplement de considérer un
Array
: un tableau de types Value est mis ensemble en mémoire de manière contiguë (directement), alors qu'en tant que tableau de types Reference, seuls la référence (un pointeur) est contiguë en mémoire; les objets pointés sont alloués séparément.L'autre problème (lié) (*) est que (presque) tous les types de référence ont la même représentation à des fins de variance et qu'une grande partie du code n'a pas besoin de connaître la différence entre les types, donc la co- et la contre-variance sont possibles (et facilement mis en œuvre - souvent simplement par omission d'une vérification de type supplémentaire).
(*) On peut considérer que c'est le même problème ...
la source