Lista <T> o IList <T> [cerrado]

495

¿Alguien puede explicarme por qué querría usar IList over List en C #?

Pregunta relacionada: ¿Por qué se considera malo exponer?List<T>

Maní
fuente
1
busque en Internet "código para una interfaz, no una implementación" y puede leer todo el día ... y encontrar algunas guerras h0ly.
granadaCoder

Respuestas:

446

Si está exponiendo su clase a través de una biblioteca que otros usarán, generalmente desea exponerla a través de interfaces en lugar de implementaciones concretas. Esto ayudará si decide cambiar la implementación de su clase más tarde para usar una clase concreta diferente. En ese caso, los usuarios de su biblioteca no necesitarán actualizar su código ya que la interfaz no cambia.

Si solo lo está usando internamente, es posible que no le importe tanto, y usarlo List<T>puede estar bien.

tvanfosson
fuente
19
No entiendo la diferencia (sutil), ¿hay algún ejemplo al que me puedas señalar?
StingyJack
134
Supongamos que originalmente usó List <T> y quería cambiar para usar CaseInsensitiveList <T>, que implementan IList <T>. Si usa el tipo concreto, todas las personas que llaman deben actualizarse. Si se expone como IList <T>, la persona que llama no tiene que cambiarse.
tvanfosson
46
Ah OKAY. Lo entiendo. Elegir el mínimo común denominador para un contrato. ¡Gracias!
StingyJack
16
Este principio es un ejemplo de encapsulación, uno de los tres pilares de la programación orientada a objetos. La idea es que oculte los detalles de implementación del usuario y, en su lugar, les proporcione una interfaz estable. Esto es para reducir la dependencia de los detalles que pueden cambiar en el futuro.
Jason
14
Tenga en cuenta también que T []: IList <T>, por lo que por razones de rendimiento siempre puede cambiar de devolver una Lista <T> de su procedimiento a devolver una T [], y si se declara que el procedimiento devuelve IList <T> (o quizás ICollection <T>, o IEnumerable <T>) nada más necesitaría ser cambiado.
yfeldblum
322

La respuesta menos popular es que a los programadores les gusta fingir que su software se reutilizará en todo el mundo, cuando de hecho la mayoría de los proyectos serán mantenidos por una pequeña cantidad de personas y por agradables que sean los fragmentos de sonido relacionados con la interfaz, estás engañando tú mismo.

Arquitectura Astronautas . Las posibilidades de que escriba alguna vez su propia IList que agregue algo a las que ya están en .NET Framework son tan remotas que son teorías gelatinosas reservadas para "mejores prácticas".

Astronautas de software

Obviamente, si se le pregunta qué usa en una entrevista, dice ILista, sonríe y ambos se ven complacidos por ser tan listos. O para una API pública, IList. Espero que entiendas mi punto.

Arec Barrwin
fuente
65
Tengo que estar en desacuerdo con tu respuesta sardónica. No estoy totalmente en desacuerdo con el sentimiento de que la sobre arquitectura sea un problema real. Sin embargo, creo que, especialmente en el caso de las colecciones, las interfaces realmente brillan. Digamos que tengo una función que devuelve IEnumerable<string>, dentro de la función puedo usar un List<string>para un almacén de respaldo interno para generar mi colección, pero solo quiero que las personas que llaman enumeren su contenido, no agreguen o eliminen. Aceptar una interfaz como parámetro comunica un mensaje similar: "Necesito una colección de cadenas, sin embargo, no se preocupe, no la cambiaré".
joshperry
38
El desarrollo de software se trata de traducir la intención. Si crees que las interfaces son útiles solo para construir arquitecturas grandiosas y de gran tamaño y no tienen lugar en pequeñas tiendas, entonces espero que la persona sentada frente a ti en la entrevista no sea yo.
joshperry
48
Alguien tenía que decir este Arec ... incluso si tienes todo tipo de patrón académico persiguiendo el correo de odio como resultado. +1 para todos los que lo odiamos cuando una pequeña aplicación está cargada de interfaces y hacer clic en "buscar definición" nos lleva a otro lugar que no sea la fuente del problema ... ¿Me prestas la frase "Astronautas de la arquitectura"? Puedo ver que será útil.
Gats
66
Si fuera posible, te daré 1000 puntos por esta respuesta. : D
Samuel
8
Estaba hablando del resultado más amplio de la persecución de patrones. Interfaces para interfaces comunes buenas, capas adicionales para perseguir un patrón, malas.
Gats
186

La interfaz es una promesa (o un contrato).

Como siempre sucede con las promesas, cuanto más pequeñas, mejor .

Rinat Abdullin
fuente
56
¡No siempre es mejor, solo que es más fácil de mantener! :)
Kirk Broadhurst el
55
No entiendo esto. Así IListes la promesa. ¿Cuál es la promesa más pequeña y mejor aquí? Esto no es lógico.
nawfal
3
Para cualquier otra clase, estaría de acuerdo. Pero no para List<T>, porque AddRangeno está definido en la interfaz.
Jesse de Wit
3
Esto no es realmente útil en este caso particular. La mejor promesa es la que se cumple. IList<T>hace promesas que no puede cumplir. Por ejemplo, hay un Addmétodo que puede lanzar si IList<T>resulta ser de solo lectura. List<T>Por otro lado, siempre se puede escribir y, por lo tanto, siempre cumple sus promesas. Además, desde .NET 4.6, ahora tenemos IReadOnlyCollection<T>y IReadOnlyList<T>que casi siempre se ajustan mejor que IList<T>. Y son covariantes. Evitar IList<T>!
ZunTzu
@ZunTzu Yo diría que alguien que pasa una colección inmutable como una mutable a sabiendas ha roto el contrato presentado, no es un problema con el contrato en sí, por ejemplo IList<T>. Alguien podría implementar IReadOnlyList<T>y hacer que cada método arroje una excepción también. Es algo opuesto a la escritura de patos: lo ha llamado pato, pero no puede graznar como uno. Sin embargo, eso es parte del contrato, incluso si el compilador no puede cumplirlo. Sin embargo, estoy 100% de acuerdo en que IReadOnlyList<T>o IReadOnlyCollection<T>debería usarse para acceso de solo lectura.
Shelby Oldfield
93

Algunas personas dicen "siempre usar en IList<T>lugar de List<T>".
Quieren que cambie sus firmas de método de void Foo(List<T> input)a void Foo(IList<T> input).

Estas personas están equivocadas.

Es más matizado que eso. Si está devolviendo una IList<T>parte de la interfaz pública a su biblioteca, se deja opciones interesantes para quizás hacer una lista personalizada en el futuro. Es posible que nunca necesite esa opción, pero es un argumento. Creo que es todo el argumento para devolver la interfaz en lugar del tipo concreto. Vale la pena mencionarlo, pero en este caso tiene una falla grave.

Como un contraargumento menor, puede encontrar que cada persona que llama necesita un de List<T>todos modos, y el código de llamada está lleno de.ToList()

Pero mucho más importante, si usted está aceptando un IList como parámetro será mejor que tener cuidado, porque IList<T>, y List<T>no se comportan de la misma manera. A pesar de la similitud en el nombre, y a pesar de compartir una interfaz , no exponen el mismo contrato .

Supongamos que tiene este método:

public Foo(List<int> a)
{
    a.Add(someNumber);
}

Un colega útil "refactoriza" el método para aceptar IList<int>.

Su código ahora está roto, porque se int[]implementa IList<int>, pero es de tamaño fijo. El contrato para ICollection<T>(la base de IList<T>) requiere el código que lo utiliza para verificar la IsReadOnlybandera antes de intentar agregar o eliminar elementos de la colección. El contrato para List<T>no.

El Principio de sustitución de Liskov (simplificado) establece que un tipo derivado debe poder usarse en lugar de un tipo base, sin condiciones previas o postcondiciones adicionales.

Esto parece que rompe el principio de sustitución de Liskov.

 int[] array = new[] {1, 2, 3};
 IList<int> ilist = array;

 ilist.Add(4); // throws System.NotSupportedException
 ilist.Insert(0, 0); // throws System.NotSupportedException
 ilist.Remove(3); // throws System.NotSupportedException
 ilist.RemoveAt(0); // throws System.NotSupportedException

Pero no lo hace. La respuesta a esto es que el ejemplo usó IList <T> / ICollection <T> incorrectamente. Si utiliza una ICollection <T>, debe verificar el indicador IsReadOnly.

if (!ilist.IsReadOnly)
{
   ilist.Add(4);
   ilist.Insert(0, 0); 
   ilist.Remove(3);
   ilist.RemoveAt(0);
}
else
{
   // what were you planning to do if you were given a read only list anyway?
}

Si alguien le pasa una Matriz o una Lista, su código funcionará bien si revisa la bandera cada vez y tiene una reserva ... Pero en realidad; quien hace eso ¿No sabe de antemano si su método necesita una lista que pueda incluir miembros adicionales? ¿no especificas eso en la firma del método? ¿Qué harías exactamente si te pasaran una lista de solo lectura int[]?

Puede sustituir un List<T>código que use IList<T>/ ICollection<T> correctamente . No puede garantizar que pueda sustituir un código IList<T>/ ICollection<T>into que utilice List<T>.

Hay una apelación al Principio de responsabilidad única / Principio de segregación de interfaz en muchos de los argumentos para usar abstracciones en lugar de tipos concretos, depende de la interfaz más estrecha posible. En la mayoría de los casos, si está utilizando List<T>ay cree que podría utilizar una interfaz más estrecha, ¿por qué no IEnumerable<T>? Esto a menudo es mejor si no necesita agregar elementos. Si es necesario agregar a la colección, utilice el tipo de hormigón, List<T>.

Para mí IList<T>(y ICollection<T>) es la peor parte del marco .NET. IsReadOnlyviola el principio de menor sorpresa. Una clase, como Array, que nunca permite agregar, insertar o eliminar elementos, no debe implementar una interfaz con los métodos Agregar, Insertar y Eliminar. (consulte también /software/306105/implementing-an-interface-when-you-dont-need-one-of-the-properties )

¿Es IList<T>una buena opción para su organización? Si un colega le pide que cambie la firma de un método para usar en IList<T>lugar de List<T>, pregúntele cómo agregaría un elemento a un IList<T>. Si no saben IsReadOnly(y la mayoría de la gente no), entonces no lo use IList<T>. Siempre.


Tenga en cuenta que el indicador IsReadOnly proviene de ICollection <T> e indica si se pueden agregar o quitar elementos de la colección; pero para confundir realmente las cosas, no indica si se pueden reemplazar, lo que en el caso de las matrices (que devuelven IsReadOnlys == verdadero) puede ser.

Para obtener más información sobre IsReadOnly, consulte la definición de msdn de ICollection <T> .IsReadOnly

perfeccionista
fuente
55
Genial, excelente respuesta. Me gustaría agregar que, incluso si IsReadOnlyes truepara un IList, ¡aún puede modificar sus miembros actuales! Quizás esa propiedad debería ser nombrada IsFixedSize?
xofz
(eso se probó con la aprobación de an Arraycomo an IList, por eso podría modificar a los miembros actuales)
xofz
1
@SamPearson había una propiedad IsFixedSize en IList en .NET 1, no incluida en IList <T> genérico de .NET 2.0, donde se consolidó con IsReadOnly, lo que condujo a un comportamiento sorprendente en torno a las matrices. Hay un blog detallado sobre las diferencias sutiles aquí: enterprisecraftsmanship.com/2014/11/22/…
perfeccionista
77
Excelente respuesta! Además, desde .NET 4.6, ahora tenemos IReadOnlyCollection<T>y IReadOnlyList<T>que casi siempre se ajustan mejor, IList<T>pero no tienen la semántica perezosa peligrosa de IEnumerable<T>. Y son covariantes. Evitar IList<T>!
ZunTzu
37

List<T>es una implementación específica de IList<T>, que es un contenedor que se puede abordar de la misma manera que una matriz lineal T[]utilizando un índice entero. Cuando especifica IList<T>como tipo de argumento del método, solo especifica que necesita ciertas capacidades del contenedor.

Por ejemplo, la especificación de la interfaz no impone una estructura de datos específica para ser utilizada. La implementación de List<T>sucede con el mismo rendimiento para acceder, eliminar y agregar elementos como una matriz lineal. Sin embargo, podría imaginar una implementación respaldada por una lista vinculada, para la cual agregar elementos al final es más barato (tiempo constante) pero el acceso aleatorio es mucho más costoso. (Tenga en cuenta que .NET LinkedList<T>no se implementa IList<T>).

Este ejemplo también le dice que puede haber situaciones en las que necesite especificar la implementación, no la interfaz, en la lista de argumentos: en este ejemplo, siempre que requiera una característica de rendimiento de acceso particular. Esto generalmente está garantizado para una implementación específica de un contenedor ( List<T>documentación: "Implementa la IList<T>interfaz genérica utilizando una matriz cuyo tamaño se incrementa dinámicamente según sea necesario").

Además, es posible que desee considerar exponer la menor funcionalidad que necesita. Por ejemplo. Si no necesita cambiar el contenido de la lista, probablemente debería considerar usar IEnumerable<T>, que se IList<T>extiende.

ILoveFortran
fuente
Con respecto a su última declaración sobre el uso de IEnumerable en lugar de IList. Esto no siempre es recomendable, tomar WPF, por ejemplo, que se acaba de crear un objeto contenedor de IList así tendrá un impacto de Potencia - msdn.microsoft.com/en-us/library/bb613546.aspx
Hades
1
Gran respuesta, las garantías de rendimiento son algo que una interfaz no puede ofrecer. En algunos códigos esto puede ser bastante importante y el uso de clases concretas comunica su intención, su necesidad de esa clase específica. Una interfaz, por otro lado, dice "Solo necesito llamar a este conjunto de métodos, no implica ningún otro contrato".
joshperry
1
@joshperry Si el rendimiento de una interfaz te molesta, estás en la plataforma equivocada en primer lugar. Impo, esto es microoptimización.
nawfal
@nawfal No estaba comentando sobre los pros / contras de rendimiento de las interfaces. Estaba de acuerdo y comentaba el hecho de que cuando eliges exponer una clase concreta como argumento, puedes comunicar una intención de rendimiento. Tomar LinkedList<T>vs tomar List<T>vs IList<T>todos comunica algo de las garantías de rendimiento requeridas por el código que se llama.
joshperry
28

Cambiaría la pregunta un poco, en lugar de justificar por qué debería usar la interfaz sobre la implementación concreta, intente justificar por qué usaría la implementación concreta en lugar de la interfaz. Si no puede justificarlo, use la interfaz.

Patrik Hägne
fuente
1
Una forma muy interesante de pensarlo. Y una buena forma de pensar al programar: gracias.
Peanut
¡Bien dicho! Dado que la interfaz es el enfoque más flexible, realmente debería justificar lo que la implementación concreta le está comprando.
Cory House
99
Gran forma de pensar. Todavía puedo responder: mi razón se llama AddRange ()
PPC
44
mi razón se llama Agregar (). a veces quieres una lista que sabes que puede tomar elementos adicionales, ¿ verdad ? Mira mi respuesta.
perfeccionista
19

IList <T> es una interfaz para que pueda heredar otra clase y aún implementar IList <T> mientras que heredar List <T> le impide hacerlo.

Por ejemplo, si hay una clase A y su clase B la hereda, entonces no puede usar la Lista <T>

class A : B, IList<T> { ... }
Diadistis
fuente
15

Un principio de TDD y OOP generalmente es programar para una interfaz, no una implementación.

En este caso específico, ya que esencialmente está hablando de una construcción de lenguaje, no personalizada, generalmente no importará, pero digamos, por ejemplo, que encontró que List no era compatible con algo que necesitaba. Si hubiera utilizado IList en el resto de la aplicación, podría extender List con su propia clase personalizada y aún así poder transmitirlo sin refactorizarlo.

El costo para hacer esto es mínimo, ¿por qué no te ahorras el dolor de cabeza más tarde? De eso se trata el principio de la interfaz.

annakata
fuente
3
Si su ExtendedList <T> hereda List <T>, aún puede incluirla en un contrato List <T>. Este argumento solo funciona si escribe su propia implementación de IList <T> desde cero (o al menos sin heredar List <T>)
PPC
14
public void Foo(IList<Bar> list)
{
     // Do Something with the list here.
}

En este caso, podría pasar en cualquier clase que implemente la interfaz IList <Bar>. Si utilizó List <Bar> en su lugar, solo se podría pasar una instancia de List <Bar>.

La forma IList <Bar> está más débilmente acoplada que la forma List <Bar>.

smack0007
fuente
13

Suponiendo que ninguna de estas preguntas (o respuestas) de List vs IList menciona la diferencia distintiva. (¡Por eso busqué esta pregunta en SO!)

Así que aquí están los métodos contenidos por List que no se encuentran en IList, al menos a partir de .NET 4.5 (circa 2015)

  • AddRange
  • AsReadOnly
  • Búsqueda binaria
  • Capacidad
  • Convertir todo
  • Existe
  • Encontrar
  • Encuentra todos
  • FindIndex
  • FindLast
  • FindLastIndex
  • Para cada
  • GetRange
  • InsertRange
  • LastIndexOf
  • Eliminar todo
  • Eliminar Rango
  • Contrarrestar
  • Ordenar
  • ToArray
  • TrimExcess
  • TrueForAll
JasonS
fuente
1
No del todo cierto. Reversey ToArrayson métodos de extensión para la interfaz IEnumerable <T>, de la cual se deriva IList <T>.
Tarec
Esto se debe a que estos solo tienen sentido en Listsy no en otras implementaciones de IList.
HankCa
12

El caso más importante para usar interfaces sobre implementaciones está en los parámetros de su API. Si su API toma un parámetro de Lista, cualquiera que lo use tiene que usar Lista. Si el tipo de parámetro es IList, la persona que llama tiene mucha más libertad y puede usar clases de las que nunca ha oído hablar, que pueden no haber existido cuando se escribió el código.

Michael Borgwardt
fuente
7

¿Qué pasa si .NET 5.0 reemplaza System.Collections.Generic.List<T>a System.Collection.Generics.LinearList<T>. .NET siempre posee el nombre List<T>pero garantiza que IList<T>es un contrato. Entonces, en mi humilde opinión, nosotros (al menos I) no deben usar el nombre de alguien (aunque es .NET en este caso) y tener problemas más tarde.

En caso de uso IList<T>, la persona que llama siempre tiene garantizado que funcionen, y el implementador es libre de cambiar la colección subyacente a cualquier implementación concreta alternativa deIList

Soundararajan
fuente
7

Todos los conceptos se expresan básicamente en la mayoría de las respuestas anteriores con respecto a por qué usar la interfaz sobre implementaciones concretas.

IList<T> defines those methods (not including extension methods)

IList<T> Enlace MSDN

  1. Añadir
  2. Claro
  3. Contiene
  4. Copiar a
  5. GetEnumerator
  6. Índice de
  7. Insertar
  8. Eliminar
  9. RemoveAt

List<T> implementa esos nueve métodos (sin incluir los métodos de extensión), además de eso tiene alrededor de 41 métodos públicos, lo que pesa en la consideración de cuál usar en su aplicación.

List<T> Enlace MSDN

yantaq
fuente
4

Lo haría porque definir una IList o una ICollection se abriría para otras implementaciones de sus interfaces.

Es posible que desee tener un IOrderRepository que defina una colección de pedidos en IList o ICollection. Luego, podría tener diferentes tipos de implementaciones para proporcionar una lista de órdenes siempre que se ajusten a las "reglas" definidas por su IList o ICollection.

Peter Lindholm
fuente
3

IList <> es casi siempre preferible según el consejo del otro póster, sin embargo, tenga en cuenta que hay un error en .NET 3.5 sp 1 al ejecutar un IList <> a través de más de un ciclo de serialización / deserialización con el WCF DataContractSerializer.

Ahora hay un SP para corregir este error: KB 971030

StuartLC
fuente
2

La interfaz garantiza que al menos obtenga los métodos que espera ; siendo consciente de la definición de la interfaz, es decir. todos los métodos abstractos que están allí para ser implementados por cualquier clase que herede la interfaz. así que si alguien hace una clase enorme con varios métodos además de los que heredó de la interfaz para alguna funcionalidad adicional, y esos no son útiles para usted, es mejor usar una referencia a una subclase (en este caso, el interfaz) y asígnele el objeto de clase concreto.

Una ventaja adicional es que su código está a salvo de cualquier cambio en la clase concreta, ya que se está suscribiendo a solo algunos de los métodos de la clase concreta y esos son los que estarán allí siempre que la clase concreta herede de la interfaz que está utilizando. así que es seguridad para usted y libertad para el codificador que está escribiendo una implementación concreta para cambiar o agregar más funcionalidad a su clase concreta.

vishy
fuente
2

Puede ver este argumento desde varios ángulos, incluido el de un enfoque puramente OO que dice programar contra una interfaz, no una implementación. Con este pensamiento, usar IList sigue el mismo principio que pasar y usar Interfaces que usted define desde cero. También creo en los factores de escalabilidad y flexibilidad proporcionados por una interfaz en general. Si una clase que implica IList <T> necesita ser extendida o cambiada, el código consumidor no tiene que cambiar; sabe a qué se adhiere el contrato de IList Interface. Sin embargo, el uso de una implementación concreta y la Lista <T> en una clase que cambia, podría hacer que el código de llamada también deba modificarse. Esto se debe a que una clase que se adhiere a IList <T> garantiza un cierto comportamiento que no está garantizado por un tipo concreto que usa List <T>.

También tener el poder de hacer algo como modificar la implementación predeterminada de la Lista <T> en una clase Implementando IList <T> por ejemplo, .Add, .Remove o cualquier otro método IList le da al desarrollador mucha flexibilidad y poder, de lo contrario predefinido por Lista <T>

atconway
fuente
0

Por lo general, un buen enfoque es usar IList en su API pública (cuando sea apropiado, y se necesita una lista semántica), y luego Listar internamente para implementar la API. Esto le permite cambiar a una implementación diferente de IList sin romper el código que usa su clase.

La lista de nombres de clase se puede cambiar en el siguiente marco .net, pero la interfaz nunca cambiará ya que la interfaz es un contrato.

Tenga en cuenta que, si su API solo se va a usar en bucles foreach, etc., entonces puede considerar exponer IEnumerable en su lugar.

lazydeveloper
fuente