¿Por qué la matriz implementa IList?

141

Consulte la definición de la clase System.Array

public abstract class Array : IList, ...

Teóricamente, debería poder escribir este bit y ser feliz

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

También debería poder llamar a cualquier método desde iList

 ilist.Add(1); //exception here

Mi pregunta no es por qué obtengo una excepción, sino por qué Array implementa IList .

oleksii
fuente
22
Buena pregunta Nunca me gustó la idea de interfaces gordas (ese es el término técnico para este tipo de diseño).
Konrad Rudolph
2
¿Alguien realmente se preocupa por el LSP? Me parece bastante académico.
Gabe
13
@Gabe, entonces necesitas trabajar con bases de código más grandes. Implementar un comportamiento (heredar de una interfaz) y luego simplemente ignorar las cosas que no le gustan o que no puede soportar conduce a mal olor, ofuscación, conversión y finalmente: código defectuoso.
Marius
3
@ Gabe es la colección que implica mutabilidad, no sus entidades contenidas. Puede hacer que su clase sea miembro de un tipo que implemente tanto IRWList <> como IReadList <>, use if como IRWList <> internamente en su clase y exponga como IReadList. Sí, tiene que poner la complejidad en alguna parte, pero no veo cómo eso se aplica a ignorar el LSP como un muy buen principio de diseño (aunque no sabía sobre la propiedad IsReadOnly, lo que hace que IList sea más complejo desde el punto de vista de los consumidores)
Marius

Respuestas:

94

Debido a que una matriz permite un acceso rápido por índice, y IList/ IList<T>son las únicas interfaces de colección que lo admiten. Entonces, tal vez su verdadera pregunta es "¿Por qué no hay una interfaz para colecciones constantes con indexadores?" Y a eso no tengo respuesta.

Tampoco hay interfaces de solo lectura para colecciones. Y me faltan esos más que una interfaz de tamaño constante con indexadores.

En mi opinión, debería haber varias interfaces de colección más (genéricas) dependiendo de las características de una colección. Y los nombres también deberían haber sido diferentes, Listporque algo con un indexador es realmente estúpido en mi opinión.

  • Solo enumeración IEnumerable<T>
  • Solo lectura pero sin indexador (.Count, .Contains, ...)
  • Redimensionable pero sin indexador, es decir, configurado como (Agregar, Eliminar, ...) actual ICollection<T>
  • Solo lectura con indexador (indexador, indexof, ...)
  • Tamaño constante con indexador (indexador con un setter)
  • Tamaño variable con indexador (Insertar, ...) actual IList<T>

Creo que las interfaces de colección actuales son de mal diseño. Pero dado que tienen propiedades que le indican qué métodos son válidos (y esto es parte del contrato de estos métodos), no se rompe el principio de sustitución.

CodesInChaos
fuente
14
gracias por la respuesta. Pero prefiero dejar la pregunta como está. El motivo es simple. La interfaz es un contrato público. Si uno lo implementa, uno debe implementar completamente a todos los miembros, de lo contrario se rompe LSP y generalmente huele mal, ¿no es así?
oleksii
16
Se rompe LSP. Si no aparece en la lista, Agregar (elemento) debería agregar un elemento a la lista independientemente del tipo concreto. Excepto en casos excepcionales. En la implementación de la matriz, se lanza una excepción en un caso no excepcional, que en sí mismo es una mala práctica
Rune FS
2
@smelch Lo siento, pero te equivocaste de LSP. Una matriz no se implementa addy, por lo tanto, no puede sustituirse por algo que sí lo hace cuando se requiere esa habilidad.
Rune FS
77
Admito que técnicamente no viola el LSP solo porque la documentación establece que debe verificar las propiedades IsFixedSizey IsReadOnly, definitivamente viola el principio Tell, Don't Ask y el Principio de menor sorpresa . ¿Por qué implementar una interfaz cuando solo vas a lanzar excepciones para 4 de los 9 métodos?
Mateo
11
Ha pasado algún tiempo desde la pregunta original. Pero ahora con .Net 4.5, hay interfaces adicionales IReadOnlyList e IReadOnlyCollection .
Tobias
43

La sección de comentarios de la documentación de IListdice

IList es un descendiente de la interfaz ICollection y es la interfaz base de todas las listas no genéricas. Las implementaciones de IList se dividen en tres categorías: solo lectura, tamaño fijo y tamaño variable . Una IList de solo lectura no se puede modificar. Una IList de tamaño fijo no permite la adición o eliminación de elementos, pero permite la modificación de elementos existentes. Una IList de tamaño variable permite la adición, eliminación y modificación de elementos.

Obviamente, las matrices caen en la categoría de tamaño fijo, por lo que por la definición de la interfaz tiene sentido.

Brian Rasmussen
fuente
44
Supongo que habrían terminado con muchas interfaces. IListFixedSize, IListReadOnly ...
Magnus
9
Esa es realmente una buena respuesta desde el punto de vista de la documentación. Pero para mí parece más bien un truco. Las interfaces deben ser delgadas y simples para que una clase implemente todos los miembros.
oleksii
1
@oleksii: estoy de acuerdo. Las interfaces y las excepciones de tiempo de ejecución no son la combinación más elegante. En defensa de Arrayesto, implementa el Addmétodo explícitamente, lo que reduce el riesgo de llamarlo por accidente.
Brian Rasmussen
Hasta que creamos una implementación de IListeso, no se permite la modificación ni la adición / eliminación. Entonces la documentación ya no es correcta. : P
Timo
1
@Magnus: en .Net 4.5, hay interfaces adicionales IReadOnlyList e IReadOnlyCollection .
RBT
17

Porque no todos los ILists son mutables (ver IList.IsFixedSizey IList.IsReadOnly), y las matrices ciertamente se comportan como listas de tamaño fijo.

Si su pregunta es realmente "por qué implementa una interfaz no genérica ", entonces la respuesta es que existían antes de que aparecieran los genéricos.

usuario541686
fuente
10
@oleksii: No, no rompe LSP, porque la interfaz en IList sí misma le dice que puede no ser mutable. Si de hecho se garantizara que fuera mutable y la matriz le dijera lo contrario, entonces rompería la regla.
user541686
En realidad, Array rompe LSP en caso de genérico IList<T>y no lo rompe en caso de no genérico IList: enterprisecraftsmanship.com/2014/11/22/…
Vladimir
5

Es un legado que tenemos de los tiempos en que no estaba claro cómo lidiar con las colecciones de solo lectura y si Array es de solo lectura. Hay indicadores IsFixedSize e IsReadOnly en la interfaz IList. El indicador IsReadOnly significa que la colección no se puede cambiar en absoluto y IsFixedSize significa que la colección permite la modificación, pero no la adición o eliminación de elementos.

En el momento de .Net 4.5, estaba claro que se requieren algunas interfaces "intermedias" para trabajar con colecciones de sólo lectura, por lo que IReadOnlyCollection<T>y IReadOnlyList<T>se introdujeron.

Aquí hay una gran publicación de blog que describe los detalles: Colecciones de solo lectura en .NET

Vladimir
fuente
0

La definición de interfaz IList es "Representa una colección no genérica de objetos a los que se puede acceder individualmente por índice". La matriz satisface completamente esta definición, por lo que debe implementar la interfaz. La excepción al llamar al método Add () es "System.NotSupportedException: la colección era de un tamaño fijo" y se produjo porque la matriz no puede aumentar su capacidad dinámicamente. Su capacidad se define durante la creación del objeto de matriz.

meir
fuente
0

Tener un arreglo implementado IList (y transitivamente, ICollection) simplificó el motor Linq2Objects, ya que convertir el IEnumerable a IList / ICollection también funcionaría para los arreglos.

Por ejemplo, un Count () termina llamando a Array.Length bajo el capó, ya que se convierte en ICollection y la implementación de la matriz devuelve Longitud.

Sin esto, el motor Linq2Objects no tendría un tratamiento especial para las matrices y funcionaría horriblemente, o tendrían que duplicar el código agregando un tratamiento de casos especiales para las matrices (como lo hacen para IList). Deben haber optado por hacer que la matriz implemente IList en su lugar.

Esa es mi opinión sobre "Por qué".

Herman Schoenfeld
fuente
0

También detalles de implementación LINQ Últimas comprobaciones de IList, si no implementa la lista necesitarían 2 comprobaciones que ralenticen todas las últimas llamadas o que el último en una matriz tome O (N)

usuario1496062
fuente