Cuándo usar IList y cuándo usar List

180

Sé que IList es la interfaz y List es el tipo concreto, pero todavía no sé cuándo usar cada una. Lo que estoy haciendo ahora es que si no necesito los métodos Ordenar o Buscar todos, uso la interfaz. Estoy en lo cierto? ¿Hay una mejor manera de decidir cuándo usar la interfaz o el tipo concreto?

Rismo
fuente
1
Si alguien todavía se pregunta, encuentro las mejores respuestas aquí: stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

Respuestas:

175

Hay dos reglas que sigo:

  • Acepte el tipo más básico que funcionará.
  • Devuelva el tipo más rico que necesitará su usuario

Entonces, al escribir una función o método que tome una colección, escríbala no para tomar una Lista, sino una IList <T>, una ICollection <T> o IEnumerable <T>. Las interfaces genéricas seguirán funcionando incluso para listas heterogéneas porque System.Object también puede ser una T. Hacer esto le ahorrará dolor de cabeza si decide usar una Pila o alguna otra estructura de datos más adelante. Si todo lo que necesita hacer en la función es foreach a través de ella, IEnumerable <T> es realmente todo lo que debe pedir.

Por otro lado, cuando devuelve un objeto fuera de una función, desea darle al usuario el conjunto de operaciones más rico posible sin que tenga que usarlo. Entonces, en ese caso, si se trata de una Lista <T> internamente, devuelva una copia como Lista <T>.

Sotavento
fuente
43
No debe tratar los tipos de entrada / salida de manera diferente. Tipos de entrada y salida deben tanto a ser el tipo más básico (preferiblemente interfaz) que apoyará las necesidades de los clientes. La encapsulación se basa en informar a los clientes lo menos posible sobre la implementación de su clase. Si devuelve una Lista concreta, no puede cambiar a otro tipo mejor sin obligar a todos sus clientes a volver a compilar / actualizar.
Ash el
11
No estoy de acuerdo con las 2 reglas ... Usaría el tipo más primitivo y especialmente cuando regrese en este caso IList (mejor IEnumarable) y debería trabajar con List en su función dentro. Luego, cuando necesite "agregar" u "ordenar", use Colección si necesita más, luego use Lista. Entonces, mi regla difícil sería: COMIENCE siempre con IENumarable y si necesita más, extienda ...
ethem
2
Para su conveniencia, las "dos reglas" tienen un nombre: principio de robustez (también conocido como la ley de Postel) .
easoncxz
Independientemente del lado del debate en el que alguien esté sobre si devolver el tipo más básico o el tipo más rico, algo a tener en cuenta es que al devolver una interfaz muy simplificada, el código de consumo a menudo, aunque no siempre, puede usar una if...elsecadena con la ispalabra clave para calcular encuentra un tipo mucho más rico y termina lanzándolo y usándolo de todos modos. Por lo tanto, no necesariamente se garantiza que oculte nada mediante el uso de una interfaz básica, en lugar de simplemente ocultarlo. Sin embargo, hacerlo más difícil también puede hacer que el escritor del código consumidor piense dos veces acerca de cómo lo están usando.
Panzercrisis
66
Estoy muy en desacuerdo con el Punto # 2, especialmente si está en un límite de servicio / API. La devolución de colecciones modificables puede dar la impresión de que las colecciones son "en vivo" y los métodos de llamada son similares Add()y Remove()pueden tener efectos más allá de la colección. Devolver una interfaz de solo lectura como IEnumerablesuele ser el camino a seguir para los métodos de recuperación de datos. Su consumidor puede proyectarlo en un tipo más rico según sea necesario.
STW
56

Las pautas de Microsoft verificadas por FxCop desalientan el uso de List <T> en API públicas; prefiera IList <T>.

Por cierto, ahora casi siempre declaro las matrices unidimensionales como IList <T>, lo que significa que puedo usar constantemente la propiedad IList <T> .Count en lugar de Array.Length. Por ejemplo:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}
Joe
fuente
3
¡Me gusta más esta explicación / ejemplo!
JonH el
28

Hay algo importante que la gente siempre parece pasar por alto:

Puede pasar una matriz simple a algo que acepte un IList<T>parámetro, y luego puede llamar IList.Add()y recibirá una excepción de tiempo de ejecución:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

Por ejemplo, considere el siguiente código:

private void test(IList<int> list)
{
    list.Add(1);
}

Si llama a eso de la siguiente manera, obtendrá una excepción de tiempo de ejecución:

int[] array = new int[0];
test(array);

Esto sucede porque el uso de matrices simples con IList<T> viola el principio de sustitución de Liskov.

Por esta razón, si llama IList<T>.Add(), puede considerar solicitar un en List<T>lugar de un IList<T>.

Matthew Watson
fuente
Esto es trivialmente cierto para cada interfaz. Si desea continuar con su argumento, entonces podría argumentar que nunca usará ninguna interfaz en absoluto, porque podría arrojar alguna implementación. Si, por el contrario, considera la sugerencia dada por la OP a preferir List<T>más IList<T>, también debe ser consciente de las razones por las que IList<T>se recomienda. (Por ejemplo, blogs.msdn.microsoft.com/kcwalina/2005/09/26/… )
Micha Wiedenmann
3
@MichaWiedenmann Mi respuesta aquí es específica de cuándo está llamando IList<T>.Add(). No estoy diciendo que no debas usar IList<T>, solo estoy señalando una posible trampa. (Que tienden a utilizar IEnumerable<T>o IReadOnlyList<T>o IReadOnlyCollection<T>con preferencia a IList<T>si puedo.)
Matthew Watson
24

Estoy de acuerdo con el consejo de Lee para tomar parámetros, pero no regresar.

Si especifica sus métodos para devolver una interfaz, eso significa que puede cambiar la implementación exacta más adelante sin que el método de consumo lo sepa. Pensé que nunca necesitaría cambiar de una Lista <T>, pero luego tuve que cambiar para usar una biblioteca de listas personalizada para la funcionalidad adicional que proporcionaba. Como solo había devuelto una IList <T>, ninguna de las personas que usaban la biblioteca tuvo que cambiar su código.

Por supuesto, eso solo necesita aplicarse a métodos que son visibles externamente (es decir, métodos públicos). Personalmente uso interfaces incluso en el código interno, pero como puede cambiar todo el código usted mismo si realiza cambios importantes, no es estrictamente necesario.

ICR
fuente
22

IEnumerable
Debe intentar usar el tipo menos específico que se adapte a su propósito.
IEnumerablees menos específico que IList.
Se usa IEnumerablecuando desea recorrer los elementos de una colección.

IList
IList implementos IEnumerable.
Debe usar IListcuando necesite acceso por índice a su colección, agregar y eliminar elementos, etc.

Lista de
List implementos IList.

rajesh
fuente
3
Excelente, respuesta clara, que marqué como útil. Sin embargo, agregaría que para la mayoría de los desarrolladores, la mayoría de las veces, no vale la pena preocuparse por la pequeña diferencia en el tamaño y el rendimiento del programa: en caso de duda, solo use una Lista.
Graham Laight
9

Siempre es mejor usar el tipo base más bajo posible. Esto le da al implementador de su interfaz, o al consumidor de su método, la oportunidad de usar lo que quiera detrás de escena.

Para las colecciones, debe intentar utilizar IEnumerable siempre que sea posible. Esto brinda la mayor flexibilidad pero no siempre es adecuado.

tgmdbm
fuente
1
Siempre es mejor aceptar el tipo de base más bajo posible. Regresar es una historia diferente. Elija qué opciones pueden ser útiles. ¿Entonces cree que su cliente podría querer usar el acceso indexado? Evite que ToList()devuelvan su devolución IEnumerable<T>que ya era una lista y devuelva una IList<T>. Ahora, los clientes pueden beneficiarse de lo que puede proporcionar sin esfuerzo.
Timo
5

Si está trabajando dentro de un único método (o incluso en una sola clase o ensamblaje en algunos casos) y nadie afuera verá lo que está haciendo, use la totalidad de una Lista. Pero si está interactuando con código externo, como cuando devuelve una lista de un método, entonces solo desea declarar la interfaz sin necesariamente vincularse a una implementación específica, especialmente si no tiene control sobre quién compila contra su código después. Si comenzó con un tipo concreto y decidió cambiar a otro, incluso si usa la misma interfaz, va a romper el código de otra persona a menos que comience con una interfaz o un tipo base abstracto.

Mark Cidade
fuente
4

No creo que haya reglas duras y rápidas para este tipo de cosas, pero generalmente sigo las pautas de usar la forma más ligera posible hasta que sea absolutamente necesario.

Por ejemplo, supongamos que tiene una Personclase y una Groupclase. Una Groupinstancia tiene muchas personas, por lo que una Lista aquí tendría sentido. Cuando declare el objeto de la lista, usaré Groupun IList<Person>y lo instanciaré como a List.

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

Y, si ni siquiera necesita todo IList, siempre puede usarlo IEnumerabletambién. Con los compiladores y procesadores modernos, no creo que haya realmente ninguna diferencia de velocidad, por lo que esto es más una cuestión de estilo.

swilliams
fuente
3
¿por qué no convertirlo en una lista en primer lugar? Todavía no entiendo por qué la prima que se obtiene de lo que es un IList a continuación en el constructor lo haces en un List <>
chobo2
Estoy de acuerdo, si está creando explícitamente un objeto List <T>, ¿pierde la ventaja de la interfaz?
The_Butcher
4

Con mayor frecuencia, es mejor utilizar el tipo utilizable más general, en este caso, la IList o incluso mejor la interfaz IEnumerable, para que pueda cambiar la implementación convenientemente más adelante.

Sin embargo, en .NET 2.0, hay una cosa molesta: IList no tiene un método Sort () . En su lugar, puede usar un adaptador suministrado:

ArrayList.Adapter(list).Sort()
petr k.
fuente
2

Debe usar la interfaz solo si la necesita, por ejemplo, si su lista se convierte en una implementación de IList que no sea List. Esto es cierto cuando, por ejemplo, usa NHibernate, que convierte ILists en un objeto de bolsa NHibernate al recuperar datos.

Si List es la única implementación que usará para una determinada colección, no dude en declararla como una implementación concreta de List.

Jon Limjap
fuente
1

En situaciones en las que me encuentro, rara vez uso IList directamente.

Por lo general, solo lo uso como argumento para un método

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

Esto me permitirá hacer un procesamiento genérico en casi cualquier matriz en .NET Framework, a menos que use IEnumerable y no IList, lo que sucede a veces.

Realmente se reduce al tipo de funcionalidad que necesita. Sugeriría usar la clase List en la mayoría de los casos. IList es mejor para cuando necesita hacer una matriz personalizada que podría tener algunas reglas muy específicas que le gustaría encapsular dentro de una colección para que no se repita, pero todavía quiere que .NET la reconozca como una lista.

Dan Herbert
fuente
1

El objeto AList le permite crear una lista, agregarle cosas, eliminarla, actualizarla, indexarla, etc. La lista se usa siempre que solo desee una lista genérica donde especifique el tipo de objeto y eso es todo.

IList por otro lado es una interfaz. Básicamente, si desea crear su propio tipo de Lista, digamos una clase de lista llamada BookList, entonces puede usar la Interfaz para darle métodos básicos y estructura a su nueva clase. IList es para cuando desea crear su propia subclase especial que implementa List.

Otra diferencia es: IList es una interfaz y no se puede instanciar. La lista es una clase y se puede instanciar. Significa:

IList<string> MyList = new IList<string>();

List<string> MyList = new List<string>
Javid
fuente