Pourquoi array implémente IList?

141

Voir la définition de la classe System.Array

public abstract class Array : IList, ...

Théoriquement, je devrais être capable d'écrire ce morceau et être heureux

int[] list = new int[] {};
IList iList = (IList)list;

Je devrais aussi pouvoir appeler n'importe quelle méthode de l'iList

 ilist.Add(1); //exception here

Ma question n'est pas pourquoi j'obtiens une exception, mais plutôt pourquoi Array implémente IList ?

oleksii
la source
22
Bonne question. Je n'ai jamais aimé l'idée de grosses interfaces (c'est le terme technique pour ce type de design).
Konrad Rudolph
2
Quelqu'un se soucie-t-il réellement de LSP? Cela me semble assez académique.
Gabe
13
@Gabe, vous devez alors travailler avec des bases de code plus volumineuses. Implémenter un comportement (hériter d'une interface) puis simplement ignorer les choses que vous n'aimez pas / ne pouvez pas supporter conduit à malodorant, obscurci, casting et enfin: code bogué.
Marius
3
@Gabe c'est la collection qui implique la mutabilité et non ses entités contenues. Vous pouvez faire de votre classe un membre d'un type qui implémente à la fois IRWList <> et IReadList <>, utiliser if comme IRWList <> en interne dans votre classe et l'exposer comme IReadList. Oui, vous devez mettre la complexité quelque part, mais je ne vois tout simplement pas comment cela s'applique au fait de ne pas tenir compte de LSP comme un très bon principe de conception (je ne connaissais pas la propriété IsReadOnly, ce qui rend IList plus complexe du point de vue des consommateurs)
Marius

Réponses:

94

Parce qu'un tableau permet un accès rapide par index, et IList/ IList<T>est sont les seules interfaces de collecte qui prennent en charge cela. Alors peut-être que votre vraie question est "Pourquoi n'y a-t-il pas d'interface pour les collections constantes avec indexeurs?" Et à cela je n'ai pas de réponse.

Il n'y a pas non plus d'interfaces en lecture seule pour les collections. Et il me manque encore plus qu'une interface de taille constante avec indexeurs.

OMI, il devrait y avoir plusieurs interfaces de collecte (génériques) supplémentaires en fonction des caractéristiques d'une collection. Et les noms auraient dû être différents aussi, Listcar quelque chose avec un indexeur est vraiment stupide IMO.

  • Juste énumération IEnumerable<T>
  • Lecture seule mais pas d'indexeur (.Count, .Contains, ...)
  • Redimensionnable mais pas d'indexeur, c'est-à-dire défini comme (Ajouter, Supprimer, ...) courant ICollection<T>
  • Lecture seule avec indexeur (indexeur, indexof, ...)
  • Taille constante avec indexeur (indexeur avec setter)
  • Taille variable avec indexeur (Insert, ...) courant IList<T>

Je pense que les interfaces de collection actuelles sont de mauvaise conception. Mais comme ils ont des propriétés vous indiquant quelles méthodes sont valides (et cela fait partie du contrat de ces méthodes), cela ne rompt pas le principe de substitution.

CodesInChaos
la source
14
Merci d'avoir répondu. Mais je laisse plutôt la question telle quelle. La raison est simple. L'interface est un marché public. Si on l'implémente, il faut implémenter pleinement tous les membres, sinon ça casse LSP et sent généralement mauvais, n'est-ce pas?
oleksii
16
Cela casse LSP. S'il ne listait pas, Add (item) devrait ajouter un item à la liste quel que soit le type concret. Sauf cas exceptionnels. Dans l'implémentation du tableau lève une exception dans un cas non exceptionnel, ce qui en soi est une mauvaise pratique
Rune FS
2
@smelch Je suis désolé mais vous vous êtes trompé sur LSP. Un tableau n'implémente pas addet ne peut donc pas être remplacé par quelque chose qui le fait lorsque cette capacité est requise.
Rune FS
7
Je concède que techniquement cela ne viole pas LSP uniquement parce que la documentation indique que vous devez vérifier les propriétés IsFixedSizeet IsReadOnly, cela viole définitivement le principe Tell, Don't Ask et le principe de la moindre surprise . Pourquoi implémenter une interface alors que vous allez simplement lancer des exceptions pour 4 des 9 méthodes?
Matthew le
11
Un certain temps s'est écoulé depuis la question initiale. Mais maintenant avec .Net 4.5, il existe des interfaces supplémentaires IReadOnlyList et IReadOnlyCollection .
Tobias
43

La section des remarques de la documentation pour IListdit

IList est un descendant de l'interface ICollection et est l'interface de base de toutes les listes non génériques. Les implémentations IList se répartissent en trois catégories: lecture seule, taille fixe et taille variable . Un IList en lecture seule ne peut pas être modifié. Un IList de taille fixe ne permet pas l'ajout ou la suppression d'éléments, mais il permet la modification d'éléments existants. Un IList de taille variable permet l'ajout, la suppression et la modification d'éléments.

De toute évidence, les tableaux tombent dans la catégorie de taille fixe, donc par la définition de l'interface, cela a du sens.

Brian Rasmussen
la source
4
Je suppose qu'ils auraient fini avec beaucoup d'interfaces. IListFixedSize, IListReadOnly ...
Magnus
9
c'est en fait une bonne réponse du point de vue de la documentation. Mais pour moi, cela ressemble plutôt à un hack. Les interfaces doivent être minces et simples pour qu'une classe implémente tous les membres.
oleksii
1
@oleksii: Je suis d'accord. Les interfaces et les exceptions d'exécution ne sont pas la combinaison la plus élégante. En défense, Arrayil met en œuvre la Addméthode explicitement, ce qui réduit le risque de l'appeler par accident.
Brian Rasmussen
Tant que nous ne créons pas une implémentation, IListcela interdit à la fois la modification et l' ajout / la suppression. Ensuite, les documentations ne sont plus correctes. : P
Timo
1
@Magnus - Dans .Net 4.5, il existe des interfaces supplémentaires IReadOnlyList et IReadOnlyCollection .
RBT
17

Parce que tous les ILists ne sont pas mutables (voir IList.IsFixedSizeet IList.IsReadOnly), et que les tableaux se comportent certainement comme des listes de taille fixe.

Si votre question est vraiment "pourquoi implémente-t-elle une interface non générique ", alors la réponse est que ces derniers existaient avant l'arrivée des génériques.

user541686
la source
10
@oleksii: Non, cela ne casse pas LSP, car l'interface IList elle-même vous dit qu'elle n'est peut-être pas modifiable. S'il était en fait garanti d'être mutable et que le tableau vous disait le contraire, alors il enfreindrait la règle.
user541686
En fait, Array casse le LSP en cas de générique IList<T>et ne le casse pas en cas de non générique IList: enterprisecraftsmanship.com/2014/11/22/...
Vladimir
5

C'est un héritage que nous avons de l'époque où il n'était pas clair comment gérer les collections en lecture seule et si Array était en lecture seule ou non. Il existe des indicateurs IsFixedSize et IsReadOnly dans l'interface IList. L'indicateur IsReadOnly signifie que la collection ne peut pas du tout être modifiée et IsFixedSize signifie que la collection autorise la modification, mais pas l'ajout ou la suppression d'éléments.

Au moment de .Net 4.5, il était clair que certaines interfaces «intermédiaires» sont nécessaires pour fonctionner avec des collections en lecture seule, IReadOnlyCollection<T>et IReadOnlyList<T>ont donc été introduites.

Voici un excellent article de blog décrivant les détails: Collections en lecture seule dans .NET

Vladimir
la source
0

La définition de l'interface IList est «Représente une collection non générique d'objets auxquels on peut accéder individuellement par index». Array satisfait complètement cette définition, il doit donc implémenter l'interface. L'exception lors de l'appel de la méthode Add () est "System.NotSupportedException: la collection était de taille fixe" et s'est produite car le tableau ne peut pas augmenter sa capacité de manière dynamique. Sa capacité est définie lors de la création de l'objet tableau.

meir
la source
0

Le fait d'avoir un tableau implémentant IList (et de manière transitoire ICollection) a simplifié le moteur Linq2Objects, car le transtypage de IEnumerable en IList / ICollection fonctionnerait également pour les tableaux.

Par exemple, un Count () finit par appeler Array.Length sous le capot, car il est converti en ICollection et l'implémentation du tableau renvoie Length.

Sans cela, le moteur Linq2Objects n'aurait pas de traitement spécial pour les tableaux et fonctionnerait horriblement, ou ils auraient besoin de doubler le code en ajoutant un traitement de cas spécial pour les tableaux (comme ils le font pour IList). Ils doivent avoir choisi de faire en sorte que le tableau implémente IList à la place.

C'est mon point de vue sur "Pourquoi".

Herman Schoenfeld
la source
0

De plus, l'implémentation détaille LINQ Last checks pour IList, si elle n'implémentait pas la liste, ils auraient besoin soit de 2 vérifications pour ralentir tous les derniers appels, soit d'avoir Last on an Array prenant O (N)

user1496062
la source