c# - generic - Lista<T> o IList<T>
return list c# (18)
¿Puede alguien explicarme por qué querría usar IList sobre Lista en C #?
Pregunta relacionada: ¿Por qué se considera malo exponer la List<T>
¿Qué pasa si .NET 5.0 reemplaza System.Collections.Generic.List<T>
por 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 (al menos yo) no debemos usar el nombre de alguien (aunque en este caso es .NET) y luego nos metemos en problemas.
En el caso de utilizar IList<T>
, la persona que llama siempre está preparada para funcionar, y el implementador es libre de cambiar la colección subyacente a cualquier implementación concreta alternativa de IList
Algunas personas dicen "siempre use IList<T>
lugar de List<T>
".
Quieren que cambies las firmas de tu 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 un IList<T>
como parte de la interfaz pública a su biblioteca, deje opciones interesantes para tal vez hacer una lista personalizada en el futuro. Puede que nunca necesites 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 un defecto grave.
Como contraargumento menor, puede encontrar que cada persona que llama necesita una List<T>
todos modos, y el código de llamada está lleno de .ToList()
Pero mucho más importante, si está aceptando un IList como parámetro, debería tener cuidado, ya que 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 int[]
implementa IList<int>
, pero tiene un tamaño fijo. El contrato para ICollection<T>
(la base de IList<T>
) requiere el código que lo utiliza para verificar el indicador IsReadOnly
antes de intentar agregar o eliminar elementos de la colección. El contrato para la List<T>
no lo hace.
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 condiciones posteriores adicionales.
Esto se siente como si rompiera 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 es así. La respuesta a esto es que el ejemplo usó IList <T> / ICollection <T> incorrecto. Si utiliza una ICollection <T>, debe marcar 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 marca la bandera cada vez y tiene un respaldo ... Pero realmente; quien hace eso ¿No sabe de antemano si su método necesita una lista que puede incluir miembros adicionales? ¿No especificas eso en la firma del método? ¿Qué iba a hacer exactamente si se le pasara una lista de solo lectura como int[]
?
Puede sustituir una List<T>
en el código que usa IList<T>
/ ICollection<T>
correctamente . No puede garantizar que puede sustituir un código IList<T>
/ ICollection<T>
en el uso de la List<T>
.
Existe 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 una List<T>
y cree que podría usar una interfaz más estrecha, ¿por qué no IEnumerable<T>
? A menudo esto es mejor si no necesita agregar elementos. Si necesita agregar a la colección, use el tipo concreto, List<T>
.
Para mí, IList<T>
(y ICollection<T>
) es la peor parte del marco .NET. IsReadOnly
viola el principio de menos 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. (vea también https://softwareengineering.stackexchange.com/questions/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 IList<T>
lugar de List<T>
, pregúnteles cómo agregarían un elemento a un IList<T>
. Si no conocen IsReadOnly
(y la mayoría de las personas no lo saben), entonces no use IList<T>
. Siempre.
Tenga en cuenta que el indicador IsReadOnly proviene de ICollection <T> e indica si se pueden agregar o eliminar elementos de la colección; pero solo para realmente confundir las cosas, no indica si se pueden reemplazar, lo que en el caso de Arrays (que devuelve IsReadOnlys == true) puede ser.
Para obtener más información sobre IsReadOnly, consulte la definición de msdn de ICollection <T> .IsReadOnly
Básicamente, todos los conceptos se expresan en la mayoría de las respuestas anteriores con respecto a por qué usar la interfaz en lugar de implementaciones concretas.
IList<T> defines those methods (not including extension methods)
IList<T>
enlace MSDN
- Añadir
- Claro
- Contiene
- Copiar a
- GetEnumerator
- Índice de
- Insertar
- retirar
- Quitar
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 influye en la consideración de cuál usar en su aplicación.
List<T>
enlace de MSDN
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, entonces cualquiera que lo use debe usar Lista. Si el tipo de parámetro es IList, entonces la persona que llama tiene mucha más libertad y puede usar clases de las que nunca escuchó, que pueden no haber existido cuando se escribió su código.
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 cuando se ejecuta un IList <> a través de más de un ciclo de serialización / deserialización con WCF DataContractSerializer.
Ahora hay un SP para solucionar este error: KB 971030
IList <T> es una interfaz para que pueda heredar otra clase y seguir implementando IList <T> mientras que heredar la Lista <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> { ... }
La respuesta menos popular es que a los programadores les gusta fingir que su software será reutilizado en todo el mundo, cuando la mayoría de los proyectos serán mantenidos por una pequeña cantidad de personas y, por muy agradables que sean las interferencias en la interfaz, lo está engañando. tú mismo.
Astronautas de la arquitectura . Las posibilidades de que alguna vez escriba su propio IList que agregue algo a las que ya están en el marco de .NET son tan remotas que es una jalea teórica reservada para las "mejores prácticas".
Obviamente, si se te pregunta qué utilizas en una entrevista, dices IList, sonríe, y ambos se ven complacidos por ser tan inteligentes. O para una API pública, IList. Espero que me entiendas.
Lo haría porque definir un IList o un ICollection se abriría para otras implementaciones de sus interfaces.
Es posible que desee tener un IOrderRepository que define una colección de órdenes en una lista 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.
Normalmente, un buen enfoque es usar IList en su API pública (cuando sea apropiado, y se necesita una lista de semánticas), 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 puede cambiarse 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 utilizar en bucles foreach, etc., es posible que desee considerar simplemente exponer IEnumerable en su lugar.
Puede ver este argumento desde varios ángulos, incluido el de un enfoque puramente OO que dice que programar contra una Interfaz, no una implementación. Con este pensamiento, el uso de IList sigue el mismo principio que el de pasar y usar las 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 implemente IList <T> necesita ser extendida o cambiada, el código consumidor no tiene que cambiar; sabe a qué se adhiere el contrato de interfaz de IList. 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 cambiarse. Esto se debe a que una clase que se adhiere a IList <T> garantiza un determinado comportamiento que no está garantizado por un tipo concreto utilizando la Lista <T>.
También tiene el poder de hacer algo como modificar la implementación predeterminada de Lista <T> en una clase Implementando IList <T> para, por ejemplo, .Add, .Remove o cualquier otro método de IList le da al desarrollador mucha flexibilidad y potencia, de lo contrario predefinido por Lista <T>
Si está exponiendo su clase a través de una biblioteca que otros usarán, generalmente querrá exponerla a través de interfaces en lugar de implementaciones concretas. Esto le ayudará si decide cambiar la implementación de su clase más adelante 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á utilizando internamente, es posible que no le importe mucho, y el uso de la List<T>
puede estar bien.
Suponiendo que ninguna de estas preguntas (o respuestas) de la lista vs la lista menciona la diferencia de la firma. (Por eso busqué esta pregunta en SO!)
Así que aquí están los métodos incluidos en la Lista 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
- Índice de búsqueda
- FindLast
- FindLastIndex
- Para cada
- GetRange
- Insertar Rango
- LastIndexOf
- Eliminar todo
- Eliminar Rango
- Marcha atrás
- Ordenar
- ToArray
- TrimExcess
- TrueForAll
Un principio de TDD y OOP generalmente es programar a una interfaz, no a una implementación.
En este caso específico, ya que básicamente se trata de una construcción de lenguaje, no una personalizada, generalmente no importará, pero digamos, por ejemplo, que List no admitió algo que necesitaba. Si hubiera usado IList en el resto de la aplicación, podría extender la Lista con su propia clase personalizada y aún así podría pasar eso sin refactorizar.
El costo para hacer esto es mínimo, ¿por qué no ahorrarse el dolor de cabeza más adelante? Es de lo que trata el principio de la interfaz.
Volvería la pregunta un poco, en lugar de justificar por qué debería usar la interfaz en lugar de la implementación concreta, trate de justificar por qué usaría la implementación concreta en lugar de la interfaz. Si no puedes justificarlo, usa la interfaz.
List<T>
es una implementación específica de IList<T>
, que es un contenedor que puede direccionarse de la misma manera que una matriz lineal T[]
usando un índice entero. Cuando especifica IList<T>
como el tipo del argumento del método, solo especifica que necesita ciertas capacidades del contenedor.
Por ejemplo, la especificación de la interfaz no obliga a utilizar una estructura de datos específica. La implementación de la List<T>
pasa al mismo rendimiento para acceder, eliminar y agregar elementos como una matriz lineal. Sin embargo, podría imaginar una implementación que esté 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 implementa IList<T>
.)
Este ejemplo también le indica 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 necesite una característica de rendimiento de acceso particular. Esto generalmente se garantiza para una implementación específica de un contenedor (Documentación de la List<T>
: "Implementa la interfaz genérica IList<T>
utilizando una matriz cuyo tamaño aumenta dinámicamente según sea necesario.").
Además, es posible que desee considerar la posibilidad de exponer la menor funcionalidad que necesita. Por ejemplo. Si no necesita cambiar el contenido de la lista, probablemente debería considerar el uso de IEnumerable<T>
, que se extiende por IList<T>
.
La interfaz garantiza que al menos obtenga los métodos que espera ; ser 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. por lo tanto, si alguien crea una gran clase propia con varios métodos además de los que heredó de la interfaz para alguna funcionalidad adicional, y esos no le son de utilidad, es mejor usar una referencia a una subclase (en este caso, el interfaz) y asignarle el objeto de clase concreto.
La ventaja adicional es que su código está a salvo de cualquier cambio en la clase concreta, ya que se suscribe 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 usted es. utilizando. por lo tanto, su seguridad y la libertad para el programador que está escribiendo una implementación concreta para cambiar o agregar más funcionalidad a su clase concreta.
La interfaz es una promesa (o un contrato).
Como siempre ocurre con las promesas, cuanto más pequeñas, mejor .
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 en su lugar usó Lista <Bar>, solo se podría pasar una instancia de Lista <Bar>.
La forma IList <Bar> está acoplada de manera más flexible que la forma List <Bar>.