¿Por qué debería usar List <T> sobre IEnumerable <T>?

24

En mi aplicación web ASP.net MVC4 utilizo IEnumerables, tratando de seguir el mantra para programar en la interfaz, no la implementación.

Return IEnumerable(Of Student)

vs

Return New List(Of Student)

La gente me dice que use List y no IEnumerable, porque las listas obligan a que se ejecute la consulta y IEumerable no.

¿Es esta realmente la mejor práctica? ¿Hay alguna alternativa? Me siento extraño usando objetos concretos donde podría usarse una interfaz. ¿Está justificado mi extraño sentimiento?

Rowan Freeman
fuente
2
Er, primero, ¿por qué es bueno forzar la ejecución de la consulta? En segundo lugar, debe monitorear y perfilar las llamadas a la base de datos para evaluar si esta consideración técnica tiene algún mérito.
user16764
2
Se dice que todas las consultas deben ejecutarse para que el modelo esté listo y cargado listo para la vista. Es decir, la vista debería recibir todo y no consultar la base de datos.
Rowan Freeman
3
Esta pregunta de StackOverflow lo cubre bastante bien.
Karl Bielefeldt
Esa es una buena respuesta, pero quiero saber si es relevante para MVC. ¿Por qué no se puede dar IEnumerables a la vista para que todas las consultas se ejecuten justo a tiempo?
Rowan Freeman el
2
"Se dice que todas las consultas deben ejecutarse para que el modelo esté listo y cargado listo para la vista. Es decir, la vista debería recibir todo y no consultar la base de datos". Eso no tiene sentido. Si pasa un IEnumerable, su vista no sabe ni le importa si está consultando la base de datos. Y así es como debe ser.
user16764

Respuestas:

21

Hay momentos en los que hacer ToList()consultas en su linq puede ser importante para garantizar que sus consultas se ejecuten en el momento y en el orden que espera. Sin embargo, esos escenarios son raros y no hay nada de qué preocuparse demasiado hasta que realmente se encuentren con ellos.

Para resumir, use en IEnumerablecualquier momento que solo necesite iteración, use IListcuando necesite indexar directamente y necesite una matriz de tamaño dinámico (si necesita indexar en una matriz de tamaño fijo, simplemente use una matriz estándar).

En cuanto al tiempo de ejecución, siempre puede usar una lista como IEnumerablevariable, así que siéntase libre de devolver un IEnumerablehaciendo un .ToList();, o pasar un parámetro como un IEnumerableejecutando .ToList()el IEnumerablepara forzar la ejecución en ese momento. Solo tenga cuidado de que cada vez que fuerce la ejecución con .ToList()usted no se aferre a la IEnumerablevariable a la que acaba de hacer eso y la ejecute nuevamente, o de lo contrario terminará duplicando las iteraciones en su consulta LINQ innecesariamente.

En lo que respecta a MVC, realmente no hay nada especial que destacar aquí. Va a seguir las mismas reglas de tiempo de ejecución que el resto de .NET, creo que podrías tener a alguien que fue mordido por la confusión causada por la semántica de ejecución retrasada en el pasado y culpó a MVC de decirte que esto está de alguna manera relacionado, pero está no. La semántica de ejecución demorada confunde a todos al principio (e incluso por un buen tiempo después; pueden ser un poco complicados). Sin embargo, una vez más, no se preocupe por eso hasta que realmente se preocupe por asegurarse de que una consulta LINQ no se ejecute dos veces o requiera que se ejecute en un cierto orden en relación con otro código, en ese momento asigne su variable a sí mismo. ToList () para forzar la ejecución y estarás bien.

Jimmy Hoffa
fuente
¿Es malo dar una vista IEnumerables? ¿Debería darle Listas para que las consultas se hayan ejecutado cuando la vista las reciba?
Rowan Freeman el
@RowanFreeman Acabo de agregar una edición para responder esto. Creo que tienes a alguien que se topó con algo que no entendía por completo (no puedo culparlo, la ejecución retrasada es muy compleja y confusa) y lo atribuyó al mal joojoo en lugar de trabajar para comprender el comportamiento completo de la ejecución retrasada semántica.
Jimmy Hoffa
Buena respuesta. Entonces, ¿alguna vez tendré que usar .ToList ()? Hasta ahora, mi aplicación funciona bien usando solo IEnumerables y pasándolos del modelo a la vista. Sin lista o .ToList (). Mi pregunta no es de funcionalidad: sé que mi aplicación funciona. Mi pregunta es una de las mejores prácticas.
Rowan Freeman
44
La mejor práctica de @RowanFreeman es utilizar la interfaz mínima que aún cumple con sus requisitos. IEnumerable está haciendo esto ahora para usted, así que no se preocupe por cambiarlo. Dicho esto, llegará un día en que una consulta se ejecutará 3 o 10 veces y no entenderá por qué, o esperará que una consulta se ejecute antes de una inserción solo para encontrar que se ejecuta después, estas son las veces que necesita reconocer. () forzará la ejecución cuando lo desee, y reiterar un IEnumerable de una consulta LINQ varias veces ejecutará la consulta completa varias veces; Arregle su ejecución cuando ocurran esos eventos
Jimmy Hoffa
1
Ni siquiera debería usar - Listpasar alrededor de un Listimplica que los contenidos de la lista van a ser modificados. Si desea devolver una colección, use IReadOnlyCollection. Listes para usar dentro de métodos y para intercambiar entre métodos que modifican la lista. ¡Eso es!
ErikE
7

Hay dos problemas

IENumerable<Data> query = MyQuery();

//Later
foreach (Data item in query) {
  //Process data
}

En el momento en que se alcanza el ciclo "Datos de proceso", la consulta ya no es válida. Por ejemplo, si la consulta se ejecuta en un DataContext que ya se ha eliminado, su código arrojará una excepción. Este tipo de cosas se vuelve muy confuso cuando procesas una consulta en un contexto diferente al que la creaste.

Un problema secundario es que su conexión no se liberará hasta que se complete el ciclo "Datos de proceso". Esto es solo un problema si "Datos de proceso" es complejo. Esto se menciona en http://msdn.microsoft.com/en-us/library/bb386929.aspx :

P. ¿Cuánto tiempo permanece abierta la conexión de mi base de datos?

A. Una conexión generalmente permanece abierta hasta que consume los resultados de la consulta. Si espera tomarse el tiempo para procesar todos los resultados y no se opone al almacenamiento en caché de los resultados, aplique ToList a la consulta. En escenarios comunes donde cada objeto se procesa solo una vez, el modelo de transmisión es superior tanto en DataReader como en LINQ to SQL.

Por lo tanto, estos problemas son la razón por la que se le recomienda asegurarse de que la consulta se ejecute realmente, por ejemplo, llamando ToList(). Sin embargo, como Jimmy sugiere, no hay nada que le impida devolver su Lista como IEnumerable.

Como regla general, recomiendo evitar iterar sobre un IEnumerable más de una vez. Suponiendo que los consumidores de su código sigan esta regla, no considero una preocupación que alguien pueda acceder a la base de datos dos veces ejecutando la consulta dos veces.

Brian
fuente
1

Otro beneficio de enumerar IEnumerabletemprano es que las excepciones se lanzarán en la ubicación adecuada. Esto ayuda a la depuración.

Por ejemplo, si tiene una excepción de punto muerto dentro de una de sus vistas de Razor, no sería tan claro como si la excepción ocurriera durante uno de sus métodos de acceso a datos.

Sam
fuente
Cualquier método que devuelva un IEnumerableque pueda arrojar probablemente esté cometiendo un error. Los IEnumerablemétodos diferidos deben dividirse en dos: un método no diferido verifica los parámetros y se configura, lanzando si es necesario (por ejemplo, debido a un argumento nulo). Luego, devuelve una llamada a la implementación privada que se aplaza. No creo que mis comentarios estén completamente en desacuerdo con su respuesta, pero sí creo que ha omitido un aspecto importante en su respuesta, que es el significado semántico de usar IEnumerablevs. a List(mutación) vs. IReadOnlyCollection(sin beneficio para aplazamiento) .
ErikE