Ainsi, comme vous le savez peut-être, les tableaux en C # implémentent IList<T>
, entre autres interfaces. D'une manière ou d'une autre, ils le font sans implémenter publiquement la propriété Count de IList<T>
! Les tableaux ont uniquement une propriété Length.
Est-ce un exemple flagrant de C # /. NET enfreignant ses propres règles sur l'implémentation de l'interface ou est-ce que je manque quelque chose?
Array
classe devait être écrite en C #!Array
est une classe "magique", qui ne pouvait pas être implémentée en C # ou dans tout autre langage ciblant .net. Mais cette fonctionnalité spécifique est disponible en C #.Réponses:
Nouvelle réponse à la lumière de la réponse de Hans
Grâce à la réponse donnée par Hans, nous pouvons voir que la mise en œuvre est un peu plus compliquée qu'on ne le pense. Le compilateur et le CLR s'efforcent tous deux de donner l'impression qu'un type de tableau implémente
IList<T>
- mais la variance de tableau rend cela plus délicat. Contrairement à la réponse de Hans, les types de tableaux (unidimensionnels, basés sur zéro de toute façon) implémentent directement les collections génériques, car le type d'un tableau spécifique ne l'est pasSystem.Array
- c'est juste le type de base du tableau. Si vous demandez à un type de tableau quelles interfaces il prend en charge, il inclut les types génériques:Production:
Pour les tableaux unidimensionnels à base zéro, en ce qui concerne le langage , le tableau est
IList<T>
également implémenté . La section 12.1.2 de la spécification C # le dit. Donc, quelle que soit l'implémentation sous-jacente, le langage doit se comporter comme si le type d'T[]
implémentation était le mêmeIList<T>
que pour toute autre interface. De ce point de vue, l'interface est implémentée avec certains des membres explicitement implémentés (commeCount
). C'est la meilleure explication au niveau linguistique de ce qui se passe.Notez que cela ne vaut que pour les tableaux unidimensionnels (et les tableaux à base zéro, pas que C # en tant que langage ne dise rien sur les tableaux à base non nulle).
T[,]
ne met pas en œuvreIList<T>
.Du point de vue de CLR, quelque chose de plus funk se passe. Vous ne pouvez pas obtenir le mappage d'interface pour les types d'interface génériques. Par exemple:
Donne une exception de:
Alors pourquoi cette bizarrerie? Eh bien, je pense que c'est vraiment dû à la covariance du tableau, qui est une verrue dans le système de types, IMO. Même si elle
IList<T>
n'est pas covariante (et ne peut pas l'être en toute sécurité), la covariance de tableau permet à cela de fonctionner:... ce qui le fait ressembler à des
typeof(string[])
outilsIList<object>
, alors que ce n'est pas vraiment le cas.La partition 1 de la spécification CLI (ECMA-335), section 8.7.1, a ceci:
...
(Il ne mentionne pas réellement
ICollection<W>
ouIEnumerable<W>
qui, je crois, est un bogue dans la spécification.)Pour la non-variance, la spécification CLI est directement associée à la spécification de langue. De la section 8.9.1 de la partition 1:
(Un vecteur est un tableau unidimensionnel avec une base nulle.)
Maintenant, en termes de détails d'implémentation , il est clair que le CLR fait un mappage génial pour conserver la compatibilité des affectations ici: quand
string[]
on demande l'implémentation deICollection<object>.Count
, il ne peut pas gérer cela de manière tout à fait normale. Cela compte-t-il comme une implémentation d'interface explicite? Je pense qu'il est raisonnable de le traiter de cette façon, car à moins que vous ne demandiez directement le mappage d'interface, il se comporte toujours de cette façon du point de vue du langage.Et quoi
ICollection.Count
?Jusqu'à présent, j'ai parlé des interfaces génériques, mais il y a ensuite le non générique
ICollection
avec saCount
propriété. Cette fois, nous pouvons obtenir le mappage de l'interface, et en fait, l'interface est implémentée directement parSystem.Array
. La documentation de l'ICollection.Count
implémentation de la propriétéArray
indique qu'elle est implémentée avec l'implémentation d'interface explicite.Si quelqu'un peut penser à une manière dont ce type d'implémentation d'interface explicite est différent de l'implémentation d'interface explicite "normale", je serais heureux de l'examiner plus en détail.
Ancienne réponse concernant l'implémentation d'interface explicite
Malgré ce qui précède, qui est plus compliqué en raison de la connaissance des tableaux, vous pouvez toujours faire quelque chose avec les mêmes effets visibles grâce à une implémentation d'interface explicite .
Voici un exemple simple et autonome:
la source
Count
est correcte - maisAdd
sera toujours lancée, car les tableaux sont de taille fixe.Eh bien, oui, euh non, pas vraiment. Voici la déclaration de la classe Array dans le framework .NET 4:
Il implémente System.Collections.IList, pas System.Collections.Generic.IList <>. Ce n'est pas possible, Array n'est pas générique. Il en va de même pour les interfaces génériques IEnumerable <> et ICollection <>.
Mais le CLR crée des types de tableaux concrets à la volée, de sorte qu'il pourrait techniquement en créer un qui implémente ces interfaces. Ceci est cependant pas le cas. Essayez ce code par exemple:
L'appel GetInterfaceMap () échoue pour un type de tableau concret avec «Interface non trouvée». Pourtant, un cast vers IEnumerable <> fonctionne sans problème.
C'est un typage de charlatans comme un canard. C'est le même type de frappe qui crée l'illusion que chaque type de valeur dérive de ValueType qui dérive d'Object. Le compilateur et le CLR ont une connaissance particulière des types de tableaux, tout comme ils le font des types valeur. Le compilateur voit votre tentative de conversion vers IList <> et dit "d'accord, je sais comment faire ça!". Et émet l'instruction IL castclass. Le CLR n'a aucun problème avec lui, il sait comment fournir une implémentation de IList <> qui fonctionne sur l'objet tableau sous-jacent. Il a une connaissance intégrée de la classe System.SZArrayHelper autrement cachée, un wrapper qui implémente réellement ces interfaces.
Ce qu'il ne fait pas explicitement comme tout le monde le prétend, la propriété Count que vous avez posée ressemble à ceci:
Oui, vous pouvez certainement appeler ce commentaire "enfreindre les règles" :) C'est par ailleurs sacrément pratique. Et extrêmement bien caché, vous pouvez vérifier cela dans SSCLI20, la distribution de source partagée pour le CLR. Recherchez "IList" pour voir où la substitution de type a lieu. Le meilleur endroit pour le voir en action est la méthode clr / src / vm / array.cpp, GetActualImplementationForArrayGenericIListMethod ().
Ce type de substitution dans le CLR est assez léger comparé à ce qui se passe dans la projection de langage dans le CLR qui permet d'écrire du code managé pour WinRT (alias Metro). À peu près n'importe quel type de noyau .NET y est remplacé. IList <> correspond à IVector <> par exemple, un type entièrement non managé. Lui-même une substitution, COM ne prend pas en charge les types génériques.
Eh bien, c'était un aperçu de ce qui se passe derrière le rideau. Cela peut être des mers très inconfortables, étranges et inconnues avec des dragons vivant à la fin de la carte. Il peut être très utile de rendre la Terre plate et de modéliser une image différente de ce qui se passe réellement dans le code managé. Le mapper à la réponse préférée de tout le monde est confortable de cette façon. Ce qui ne fonctionne pas très bien pour les types valeur (ne mute pas une structure!) Mais celui-ci est très bien caché. L'échec de la méthode GetInterfaceMap () est la seule fuite dans l'abstraction à laquelle je puisse penser.
la source
Array
classe, qui n'est pas le type d'un tableau. C'est le type de base d' un tableau. Un tableau unidimensionnel en C # est implémentéIList<T>
. Et un type non générique peut certainement implémenter une interface générique de toute façon ... ce qui fonctionne car il y a beaucoup de types différents -typeof(int[])
! = Typeof (string []), so
typeof (int []) `implémenteIList<int>
ettypeof(string[])
implémenteIList<string>
.Array
(qui, comme vous le montrez, est une classe abstraite, ne peut donc pas être le type réel d'un objet tableau) et la conclusion (qu'il ne met pas en œuvreIList<T>
) sont incorrectes IMO. La façon dont il implémenteIList<T>
est inhabituelle et intéressante, je suis d'accord - mais c'est purement un détail d' implémentation . Dire queT[]
cela ne met pas en œuvreIList<T>
est une erreur de l'OMI. Cela va à l'encontre des spécifications et de tous les comportements observés.IList<T>
parceArray
que non? Cette logique est en grande partie ce avec quoi je ne suis pas d'accord. Au-delà de cela, je pense que nous devrions nous entendre sur une définition de ce que signifie pour un type d'implémenter une interface: à mon avis, les types de tableaux affichent toutes les fonctionnalités observables des types qui implémententIList<T>
, autres queGetInterfaceMapping
. Encore une fois, la manière dont cela est réalisé est moins importante pour moi, tout comme je suis d'accord pour dire queSystem.String
c'est immuable, même si les détails de mise en œuvre sont différents.IList<T>
pour fonctionner.IList<T>.Count
est implémenté explicitement :Ceci est fait pour que lorsque vous avez une simple variable de tableau, vous ne disposez pas des deux
Count
etLength
directement disponibles.En général, l'implémentation d'interface explicite est utilisée lorsque vous voulez vous assurer qu'un type peut être utilisé d'une manière particulière, sans forcer tous les consommateurs du type à y penser de cette façon.
Edit : Oups, mauvais souvenir là-bas.
ICollection.Count
est implémenté explicitement. Le génériqueIList<T>
est traité comme Hans décrit ci-dessous .la source
string
).ICollection
déclareCount
, et ce serait encore plus déroutant si un type avec le mot "collection" ne l'utilisait pasCount
:). Il y a toujours des compromis dans la prise de ces décisions.IList<T>
, bien que les spécifications du langage et de la CLI semblent le contraire. J'ose dire que la façon dont la mise en œuvre de l'interface fonctionne sous les couvertures peut être complexe, mais c'est le cas dans de nombreuses situations. Souhaitez-vous également voter contre quelqu'un qui dit queSystem.String
c'est immuable, simplement parce que le fonctionnement interne est mutable? À toutes fins pratiques - et certainement en ce qui concerne le langage C # - il est explicite impl.Implémentation d'interface explicite . En bref, vous le déclarez comme
void IControl.Paint() { }
ouint IList<T>.Count { get { return 0; } }
.la source
Ce n'est pas différent d'une implémentation d'interface explicite d'IList. Ce n'est pas parce que vous implémentez l'interface que ses membres doivent apparaître en tant que membres de classe. Il fait mettre en œuvre la propriété Count, il ne vient pas exposer sur X [].
la source
Avec des sources de référence disponibles:
Plus précisément cette partie:
(Je souligne)
Source (faites défiler vers le haut).
la source