IEnumerable vs List - ¿Qué usar? ¿Cómo trabajan?

678

Tengo algunas dudas sobre cómo funcionan los enumeradores y LINQ. Considere estas dos selecciones simples:

List<Animal> sel = (from animal in Animals 
                    join race in Species
                    on animal.SpeciesKey equals race.SpeciesKey
                    select animal).Distinct().ToList();

o

IEnumerable<Animal> sel = (from animal in Animals 
                           join race in Species
                           on animal.SpeciesKey equals race.SpeciesKey
                           select animal).Distinct();

Cambié los nombres de mis objetos originales para que parezca un ejemplo más genérico. La consulta en sí no es tan importante. Lo que quiero preguntar es esto:

foreach (Animal animal in sel) { /*do stuff*/ }
  1. Me di cuenta de que si utilizo IEnumerable, cuando depuro e inspecciono "sel", que en ese caso es el IEnumerable, tiene algunos miembros interesantes: "inner", "external", "innerKeySelector" y "externalKeySelector", estos últimos 2 aparecen ser delegados El miembro "interno" no tiene instancias de "Animal", sino instancias de "Especies", lo cual fue muy extraño para mí. El miembro "externo" contiene instancias "animales". Supongo que los dos delegados determinan qué entra y qué sale.

  2. Me di cuenta de que si uso "Distinct", el "interno" contiene 6 elementos (esto es incorrecto ya que solo 2 son distintos), pero el "externo" contiene los valores correctos. De nuevo, probablemente los métodos delegados determinan esto, pero esto es un poco más de lo que sé sobre IEnumerable.

  3. Lo más importante, ¿cuál de las dos opciones es la mejor en cuanto a rendimiento?

La conversión Lista mal a través de .ToList()?

¿O tal vez usando el enumerador directamente?

Si puede, explique también un poco o arroje algunos enlaces que expliquen este uso de IEnumerable.

Axonn
fuente

Respuestas:

741

IEnumerabledescribe el comportamiento, mientras que List es una implementación de ese comportamiento. Cuando lo usa IEnumerable, le da al compilador la oportunidad de diferir el trabajo hasta más tarde, posiblemente optimizando en el camino. Si usa ToList (), fuerza al compilador a reificar los resultados de inmediato.

Siempre que estoy "apilando" expresiones LINQ, utilizo IEnumerable, porque al especificar el comportamiento solo le doy a LINQ la oportunidad de diferir la evaluación y posiblemente optimizar el programa. ¿Recuerdas que LINQ no genera el SQL para consultar la base de datos hasta que la enumeras? Considera esto:

public IEnumerable<Animals> AllSpotted()
{
    return from a in Zoo.Animals
           where a.coat.HasSpots == true
           select a;
}

public IEnumerable<Animals> Feline(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Felidae"
           select a;
}

public IEnumerable<Animals> Canine(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Canidae"
           select a;
}

Ahora tiene un método que selecciona una muestra inicial ("AllSpotted"), más algunos filtros. Entonces ahora puedes hacer esto:

var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());

Entonces, ¿es más rápido usar List over IEnumerable? Solo si desea evitar que una consulta se ejecute más de una vez. ¿Pero es mejor en general? Bueno, en lo anterior, los leopardos y las hienas se convierten en consultas SQL individuales cada uno , y la base de datos solo devuelve las filas que son relevantes. Pero si hubiéramos devuelto una Lista de AllSpotted(), entonces podría funcionar más lentamente porque la base de datos podría devolver muchos más datos de los que realmente se necesitan, y desperdiciamos ciclos haciendo el filtrado en el cliente.

En un programa, puede ser mejor diferir la conversión de su consulta a una lista hasta el final, por lo que si voy a enumerar a través de Leopardos y Hienas más de una vez, haría esto:

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();
Chris Wenham
fuente
11
Creo que se refieren a los dos lados de una unión. Si hace "SELECT * FROM Animals JOIN Species ...", entonces la parte interna de la combinación es Animals, y la parte externa es Species.
Chris Wenham
10
Cuando he leído las respuestas sobre: IEnumerable <T> vs IQueryable <T> , vi la explicación analógica, por lo que IEnumerable fuerza automáticamente el tiempo de ejecución para usar LINQ to Objects para consultar la colección. Entonces estoy confundido entre estos 3 tipos. stackoverflow.com/questions/2876616/…
Bronek
44
@Bronek La respuesta que vinculó es verdadera. IEnumerable<T>será LINQ-To-Objects después de la primera parte, lo que significa que todos los vistos tendrían que ser devueltos para ejecutar Feline. Por otro lado, un IQuertable<T>permitirá que la consulta sea refinada, derribando solo felinos manchados.
Nate
21
¡Esta respuesta es muy engañosa! El comentario de @ Nate explica por qué. Si está utilizando IEnumerable <T>, el filtro va a suceder en el lado del cliente, pase lo que pase.
Hans
55
Sí, AllSpotted () se ejecutará dos veces. El mayor problema con esta respuesta es la siguiente declaración: "Bueno, en lo anterior, los leopardos y las hienas se convierten en consultas SQL individuales cada uno, y la base de datos solo devuelve las filas que son relevantes". Esto es falso, porque la cláusula where se llama en un IEnumerable <> y eso solo sabe cómo recorrer los objetos que ya provienen de la base de datos. Si realizó el retorno de AllSpotted () y los parámetros de Feline () y Canine () en IQueryable, entonces el filtro sucedería en SQL y esta respuesta tendría sentido.
Hans
178

Hay un muy buen artículo escrito por: Claudio Bernasconi TechBlog aquí: Cuándo usar IEnumerable, ICollection, IList and List

Aquí algunos puntos básicos sobre escenarios y funciones:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

rubStackOverflow
fuente
25
Cabe señalar que este artículo es solo para el público frente a partes de su código, no para el funcionamiento interno. Listes una implementación de IListy como tal tiene una funcionalidad adicional en la parte superior de los de IList(por ejemplo Sort, Find, InsertRange). Si te obligas a usar IListmás List, pierdes estos métodos que puedes necesitar
Jonathan Twite el
44
No lo olvidesIReadOnlyCollection<T>
Dandré
2
Puede ser útil incluir una matriz simple []aquí también.
jbyrd
Si bien puede estar mal visto, gracias por compartir este gráfico y artículo
Daniel
134

Una clase que implementa le IEnumerablepermite usar la foreachsintaxis.

Básicamente tiene un método para obtener el siguiente elemento de la colección. No necesita que toda la colección esté en la memoria y no sabe cuántos elementos contiene, foreachsolo sigue obteniendo el siguiente elemento hasta que se agota.

Esto puede ser muy útil en ciertas circunstancias, por ejemplo, en una tabla de base de datos masiva, no desea copiar todo en la memoria antes de comenzar a procesar las filas.

Ahora Listimplementa IEnumerable, pero representa la colección completa en la memoria. Si tiene un IEnumerabley llama .ToList(), crea una nueva lista con el contenido de la enumeración en la memoria.

Su expresión linq devuelve una enumeración y, de forma predeterminada, la expresión se ejecuta cuando itera utilizando foreach. Una IEnumerableinstrucción linq se ejecuta cuando itera foreach, pero puede forzarla a iterar antes usando .ToList().

Esto es lo que quiero decir:

var things = 
    from item in BigDatabaseCall()
    where ....
    select item;

// this will iterate through the entire linq statement:
int count = things.Count();

// this will stop after iterating the first one, but will execute the linq again
bool hasAnyRecs = things.Any();

// this will execute the linq statement *again*
foreach( var thing in things ) ...

// this will copy the results to a list in memory
var list = things.ToList()

// this won't iterate through again, the list knows how many items are in it
int count2 = list.Count();

// this won't execute the linq statement - we have it copied to the list
foreach( var thing in list ) ...
Keith
fuente
2
Pero, ¿qué sucede si ejecuta un foreach en un IEnumerable sin convertirlo primero en una Lista ? ¿Trae toda la colección en la memoria? O, ¿crea una instancia del elemento uno por uno, mientras itera sobre el bucle foreach? gracias
Pap
@Pap el último: se ejecuta nuevamente, nada se almacena automáticamente en la memoria caché.
Keith
Parece que la diferencia clave es 1) todo en la memoria o no. 2) IEnumerable déjame usar foreachmientras List irá por dicho índice. Ahora, si quisiera saber el conteo / duración de thingantemano, IEnumerable no ayudará, ¿verdad?
Jeb50
@ Jeb50 No exactamente, tanto Liste Arrayimplementar IEnumerable. Puede considerarse IEnumerablecomo el mínimo común denominador que funciona tanto en colecciones de memoria como en grandes que obtienen un elemento a la vez. Cuando llame IEnumerable.Count(), puede estar llamando a una .Lengthpropiedad rápida o revisando toda la colección; el punto es que con IEnumerableusted no sabe. Eso puede ser un problema, pero si solo va a foreachhacerlo, entonces no le importa: su código funcionará con uno Arrayo DataReaderel mismo.
Keith
1
@MFouadKajj No sé qué pila está usando, pero es casi seguro que no hace una solicitud con cada fila. El servidor ejecuta la consulta y calcula el punto de partida del conjunto de resultados, pero no obtiene todo. Para conjuntos de resultados pequeños, es probable que se trate de un solo viaje, para los grandes está enviando una solicitud de más filas de los resultados, pero no vuelve a ejecutar la consulta completa.
Keith
97

Nadie mencionó una diferencia crucial, irónicamente respondió a una pregunta cerrada como un duplicado de esto.

IEnumerable es de solo lectura y List no.

Consulte la diferencia práctica entre List y IEnumerable

Tipo CAD
fuente
Como seguimiento, ¿se debe al aspecto de la interfaz o al aspecto de la lista? es decir, ¿IList también es de solo lectura?
Jason Masters
IList no es de solo lectura: docs.microsoft.com/en-us/dotnet/api/… IEnumerable es de solo lectura porque carece de cualquier método para agregar o eliminar algo una vez que se construye, es una de las interfaces base que IList extiende (ver enlace)
CAD bloke
67

Lo más importante a tener en cuenta es que, utilizando Linq, la consulta no se evalúa de inmediato. Solo se ejecuta como parte de iterar a través del resultado IEnumerable<T>en un foreach- eso es lo que están haciendo todos los delegados extraños.

Entonces, el primer ejemplo evalúa la consulta inmediatamente llamando ToListy colocando los resultados de la consulta en una lista.
El segundo ejemplo devuelve un IEnumerable<T>que contiene toda la información necesaria para ejecutar la consulta más adelante.

En términos de rendimiento, la respuesta es que depende . Si necesita que los resultados se evalúen de inmediato (por ejemplo, está mutando las estructuras que está consultando más adelante, o si no desea que la iteración IEnumerable<T>tome mucho tiempo) use una lista. De lo contrario, use un IEnumerable<T>. El valor predeterminado debería ser usar la evaluación a pedido en el segundo ejemplo, ya que generalmente usa menos memoria, a menos que haya una razón específica para almacenar los resultados en una lista.

thecoop
fuente
Hola y gracias por responder :: -). Esto despejó casi todas mis dudas. ¿Alguna idea de por qué el Enumerable está "dividido" en "interno" y "externo"? Esto sucede cuando inspecciono el elemento en modo de depuración / interrupción con el mouse. ¿Es esta quizás la contribución de Visual Studio? ¿Enumerando en el acto e indicando la entrada y salida de Enum?
Axonn
55
Ese es el Jointrabajo: el interior y el exterior son los dos lados de la unión. En general, no se preocupe por lo que hay realmente en el IEnumerables, ya que será completamente diferente de su código real. Solo preocúpate por la salida real cuando iteras sobre ella :)
thecoop
40

La ventaja de IEnumerable es la ejecución diferida (generalmente con bases de datos). La consulta no se ejecutará hasta que realmente recorra los datos. Es una consulta esperando hasta que sea necesaria (también conocida como carga diferida).

Si llama a ToList, la consulta se ejecutará o se "materializará" como me gusta decir.

Hay pros y contras para ambos. Si llama a ToList, puede eliminar algún misterio sobre cuándo se ejecuta la consulta. Si te quedas con IEnumerable, obtienes la ventaja de que el programa no hace ningún trabajo hasta que realmente sea necesario.

Matt Sherman
fuente
25

Compartiré un concepto mal utilizado en el que caí un día:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));


// updating existing list
names[0] = "ford";

// Guess what should be printed before continuing
print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Resultado Esperado

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari

Resultado actual

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari

Explicación

Según otras respuestas, la evaluación del resultado fue diferida hasta la llamada ToListo métodos de invocación similares, por ejemploToArray .

Entonces puedo reescribir el código en este caso como:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

// updating existing list
names[0] = "ford";

// before calling ToList directly
var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));

print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Jugar alrededor

https://repl.it/E8Ki/0

amd
fuente
1
Esto se debe a los métodos linq (extensión) que en este caso provienen de IEnumerable donde solo se crea una consulta pero no se ejecuta (detrás de escena se usan los árboles de expresión). De esta manera, tiene la posibilidad de hacer muchas cosas con esa consulta sin tocar los datos (en este caso, datos en la lista). El método de lista toma la consulta preparada y la ejecuta contra la fuente de datos.
Bronek
2
En realidad, leí todas las respuestas, y la suya fue la que voté, porque establece claramente la diferencia entre las dos sin hablar específicamente de LINQ / SQL. Es esencial saber todo esto ANTES de llegar a LINQ / SQL. Admirar.
BeemerGuy
Esa es una diferencia importante para explicar, pero su "resultado esperado" no es realmente esperado. Lo dices como si fuera una especie de trampa en lugar de diseño.
Neme
@Neme, sí Era mi expectativa antes de entender cómo IEnumerablefunciona, pero ahora no es más, ya que sé cómo;)
amd
15

Si todo lo que quiere hacer es enumerarlos, use el IEnumerable.

Sin embargo, tenga en cuenta que cambiar la colección original que se enumera es una operación peligrosa; en este caso, ToListprimero querrá hacerlo . Esto creará un nuevo elemento de lista para cada elemento en la memoria, enumerando IEnumerabley, por lo tanto, será menos eficaz si solo enumera una vez, pero es más seguro y, a veces, los Listmétodos son útiles (por ejemplo, en acceso aleatorio).

Daren Thomas
fuente
1
No estoy seguro de que sea seguro decir que generar una lista significa un menor rendimiento.
Steven Sudit
@ Steven: de hecho, como dijeron Thecoop y Chris, a veces puede ser necesario usar una Lista. En mi caso, he concluido que no lo es. @ Daren: ¿qué quieres decir con "esto creará una nueva lista para cada elemento en la memoria"? ¿Quizás quiso decir una "entrada de la lista"? :: -).
Axonn
@Axonn sí, mencioné la entrada de la lista. fijo.
Daren Thomas el
@Steven Si planea iterar sobre los elementos en el IEnumerable, crear primero una lista (e iterar sobre eso) significa iterar sobre los elementos dos veces . Entonces, a menos que desee realizar operaciones que sean más eficientes en la lista, esto realmente significa un menor rendimiento.
Daren Thomas
3
@jerhewet: nunca es una buena idea modificar una secuencia que se repite. Cosas malas sucederán. Las abstracciones se filtrarán. Los demonios irrumpirán en nuestra dimensión y causarán estragos. Entonces sí, .ToList()ayuda aquí;)
Daren Thomas
5

Además de todas las respuestas publicadas anteriormente, aquí están mis dos centavos. Hay muchos otros tipos además de List que implementa IEnumerable como ICollection, ArrayList, etc. Así que si tenemos IEnumerable como parámetro de cualquier método, podemos pasar cualquier tipo de colección a la función. Es decir, podemos tener un método para operar en abstracción, no en ninguna implementación específica.

Ananth
fuente
1

Hay muchos casos (como una lista infinita o una lista muy grande) donde IEnumerable no se puede transformar en una Lista. Los ejemplos más obvios son todos los números primos, todos los usuarios de Facebook con sus detalles o todos los artículos en eBay.

La diferencia es que los objetos "Lista" se almacenan "aquí y ahora", mientras que los objetos "IEnumerable" funcionan "solo uno a la vez". Entonces, si estoy revisando todos los elementos en eBay, uno a la vez sería algo que incluso una computadora pequeña puede manejar, pero ".ToList ()" seguramente me dejaría sin memoria, sin importar cuán grande fuera mi computadora. Ninguna computadora puede por sí misma contener y manejar una cantidad tan grande de datos.

[Editar] - No hace falta decir que no es "ni esto ni aquello". a menudo tendría sentido usar una lista y un IEnumerable en la misma clase. Ninguna computadora en el mundo podría enumerar todos los números primos, porque por definición esto requeriría una cantidad infinita de memoria. Pero podría pensar fácilmente en un class PrimeContainerque contiene un IEnumerable<long> primes, que por razones obvias también contiene un SortedList<long> _primes. Todos los números primos calculados hasta ahora. el próximo primo que se comprobará solo se ejecutará contra los primos existentes (hasta la raíz cuadrada). De esa manera, obtienes ambos: números primos uno a la vez (IEnumerable) y una buena lista de "números primos hasta ahora", que es una aproximación bastante buena de toda la lista (infinita).

LongChalk
fuente