Pregunta

¿Puede alguien explicarme por qué me gustaría usar IList en Lista en C #?

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

¿Fue útil?

Solución

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 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 List < T > puede estar bien.

Otros consejos

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, te estás engañando.

Astronautas de arquitectura . Las posibilidades de que alguna vez escriba su propio IList que agregue algo a los que ya están en el marco de .NET son tan remotas que es una jalea teórica reservada para las "mejores prácticas".

Software astronautas

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 tengas mi punto.

La interfaz es una promesa (o un contrato).

Como sucede siempre con las promesas: más pequeño, mejor .

Algunas personas dicen que "siempre use IList < T > en lugar de List < T > " ;.
Quieren que cambies las firmas de tu método de void Foo (lista < 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, puede elegir opciones interesantes para 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 un List < T > de 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 tienes este método:

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

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

Su código ahora está roto porque 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 el indicador IsReadOnly antes de intentar para añadir o eliminar elementos de la colección. El contrato para 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.

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 lo hace. La respuesta a esto es que el ejemplo utiliza IList < T > / ICollection < T > incorrecto. Si utiliza un ICollection < T > necesitas marcar la bandera de 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é harías exactamente si te pasaran una lista de solo lectura como int [] ?

Puede sustituir una List < T > en el código que utiliza IList < T > / ICollection < T > correctamente . Usted no puede garantizar que puede sustituir un IList < T > / ICollection < T > en el código que usa List < T > .

En muchos de los argumentos para utilizar abstracciones en lugar de tipos concretos, se apela al Principio de responsabilidad única / Principio de segregación de la interfaz. En la mayoría de los casos, si está utilizando una Lista < 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 / implementando una interfaz cuando usted no necesita una de las propiedades )

¿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 utilizar IList < T > en 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 la gente no lo sabe), no use IList < T > . Siempre.


Tenga en cuenta que el indicador IsReadOnly proviene de ICollection < T > y 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 más información sobre IsReadOnly, vea msdn definición de ICollection < T > .IsReadOnly

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 [] utilizando un índice de enteros. 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 List < T > tiene el mismo rendimiento para acceder, eliminar y agregar elementos como una matriz lineal. Sin embargo, puede 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 not 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 requiera una característica de rendimiento de acceso particular. Esto generalmente se garantiza para una implementación específica de un contenedor ( List < T > documentación: " Implementa la interfaz genérica IList < T > usando una matriz cuyo tamaño es dinámico aumentado 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 IList < T > amplía.

Volvería un poco la pregunta, en lugar de justificar por qué debería usar la interfaz en lugar de 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.

IList < T > es una interfaz para que pueda heredar otra clase y seguir implementando IList < T > mientras hereda la Lista < T > te impide hacerlo.

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

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

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

En este caso específico, ya que básicamente se trata de una construcción de lenguaje, no de 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 pasarla sin refactorizar.

El costo de 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.

public void Foo(IList<Bar> list)
{
     // Do Something with the list here.
}

En este caso, podría pasar en cualquier clase que implemente IList < Bar > interfaz. Si utilizó Lista < Bar > en su lugar, solo una lista < Bar > La instancia podría pasar.

El IList < Bar > way está más débilmente acoplado que la Lista < Bar > manera.

El caso más importante para usar interfaces en implementaciones está en los parámetros de su API. Si su API toma un parámetro de Lista, cualquier persona 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.

Suponiendo que ninguna de estas preguntas de la Lista frente a las de IList (o respuestas) menciona la diferencia de la firma. (¡Por eso busqué esta pregunta en SO!)

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

  • AddRange
  • AsReadOnly
  • BinarySearch
  • capacidad
  • ConvertAll
  • Existe
  • Buscar
  • FindAll
  • FindIndex
  • FindLast
  • FindLastIndex
  • ForEach
  • GetRange
  • InsertRange
  • LastIndexOf
  • Eliminar todo
  • RemoveRange
  • Invertir
  • Ordenar
  • ToArray
  • TrimExcess
  • TrueForAll

¿Qué pasaría 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 tendremos problemas.

En el caso de usar IList < T > , la persona que llama siempre está preparada para trabajar, y el implementador es libre de cambiar la colección subyacente a cualquier implementación concreta alternativa de IList

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 de MSDN

  1. añadir
  2. Borrar
  3. Contiene
  4. CopyTo
  5. GetEnumerator
  6. IndexOf
  7. Insertar
  8. Eliminar
  9. RemoveAt

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

Lista < T > enlace de MSDN

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 defina una colección de órdenes en una lista IList o ICollection. Luego, podría tener diferentes tipos de implementaciones para proporcionar una lista de pedidos siempre que se ajusten a las reglas " " definido por su IList o ICollection.

La interfaz garantiza que al menos obtengas los métodos que estás esperando ; ser consciente de la definición de la interfaz es decir. todos los métodos abstractos que haya que implementar 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 a 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í mientras la clase concreta herede de interfaz que está 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 funciones a su clase concreta.

IList < > casi siempre es preferible según el consejo del otro póster, sin embargo, la nota 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 corregir este error: KB 971030

Puedes ver este argumento desde varios ángulos, incluido el de un enfoque puramente OO que dice que debes programar contra una Interfaz y no una implementación. Con este pensamiento, el uso de IList sigue el mismo principio que el de 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 implementa IList < T > necesita ser extendido o cambiado, el código de consumo no tiene que cambiar; sabe a qué se adhiere el contrato de interfaz de IList. Sin embargo, utilizando una implementación concreta y List < T > en una clase que cambia, podría causar 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 List < T > ;.

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

Por lo general, 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. & nbsp; Esto te permite cambiar a una implementación diferente de IList sin romper el código que usa tu 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 usará en bucles foreach, etc., es posible que desee considerar simplemente exponer IEnumerable en su lugar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top