¿Alguien puede explicarme por qué querría usar IList over List en C #?
Pregunta relacionada: ¿Por qué se considera malo exponer?List<T>
¿Alguien puede explicarme por qué querría usar IList over List en C #?
Pregunta relacionada: ¿Por qué se considera malo exponer?List<T>
Respuestas:
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.fuente
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".
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.
fuente
IEnumerable<string>
, dentro de la función puedo usar unList<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é".La interfaz es una promesa (o un contrato).
Como siempre sucede con las promesas, cuanto más pequeñas, mejor .
fuente
IList
es la promesa. ¿Cuál es la promesa más pequeña y mejor aquí? Esto no es lógico.List<T>
, porqueAddRange
no está definido en la interfaz.IList<T>
hace promesas que no puede cumplir. Por ejemplo, hay unAdd
método que puede lanzar siIList<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 tenemosIReadOnlyCollection<T>
yIReadOnlyList<T>
que casi siempre se ajustan mejor queIList<T>
. Y son covariantes. EvitarIList<T>
!IList<T>
. Alguien podría implementarIReadOnlyList<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 queIReadOnlyList<T>
oIReadOnlyCollection<T>
debería usarse para acceso de solo lectura.Algunas personas dicen "siempre usar en
IList<T>
lugar deList<T>
".Quieren que cambie sus firmas de método de
void Foo(List<T> input)
avoid 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>
, yList<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:
Un colega útil "refactoriza" el método para aceptar
IList<int>
.Su código ahora está roto, porque se
int[]
implementaIList<int>
, pero es de tamaño fijo. El contrato paraICollection<T>
(la base deIList<T>
) requiere el código que lo utiliza para verificar laIsReadOnly
bandera antes de intentar agregar o eliminar elementos de la colección. El contrato paraList<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.
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.
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 useIList<T>
/ICollection<T>
correctamente . No puede garantizar que pueda sustituir un códigoIList<T>
/ICollection<T>
into que utiliceList<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é noIEnumerable<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>
(yICollection<T>
) es la peor parte del marco .NET.IsReadOnly
viola el principio de menor sorpresa. Una clase, comoArray
, 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 enIList<T>
lugar deList<T>
, pregúntele cómo agregaría un elemento a unIList<T>
. Si no sabenIsReadOnly
(y la mayoría de la gente no), entonces no lo useIList<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
fuente
IsReadOnly
estrue
para unIList
, ¡aún puede modificar sus miembros actuales! Quizás esa propiedad debería ser nombradaIsFixedSize
?Array
como anIList
, por eso podría modificar a los miembros actuales)IReadOnlyCollection<T>
yIReadOnlyList<T>
que casi siempre se ajustan mejor,IList<T>
pero no tienen la semántica perezosa peligrosa deIEnumerable<T>
. Y son covariantes. EvitarIList<T>
!List<T>
es una implementación específica deIList<T>
, que es un contenedor que se puede abordar de la misma manera que una matriz linealT[]
utilizando un índice entero. Cuando especificaIList<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 .NETLinkedList<T>
no se implementaIList<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 laIList<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 seIList<T>
extiende.fuente
LinkedList<T>
vs tomarList<T>
vsIList<T>
todos comunica algo de las garantías de rendimiento requeridas por el código que se llama.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.
fuente
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>
fuente
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.
fuente
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>.
fuente
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)
fuente
Reverse
yToArray
son métodos de extensión para la interfaz IEnumerable <T>, de la cual se deriva IList <T>.List
sy no en otras implementaciones deIList
.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.
fuente
¿Qué pasa si .NET 5.0 reemplaza
System.Collections.Generic.List<T>
aSystem.Collection.Generics.LinearList<T>
. .NET siempre posee el nombreList<T>
pero garantiza queIList<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
fuente
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>
Enlace MSDNList<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 MSDNfuente
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.
fuente
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
fuente
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.
fuente
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>
fuente
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.
fuente