¿Debería siempre devolver IEnumerable <T> en lugar de IList <T>?

97

Cuando escribo mi DAL u otro código que devuelve un conjunto de elementos, ¿debo siempre hacer mi declaración de devolución?

public IEnumerable<FooBar> GetRecentItems()

o

public IList<FooBar> GetRecentItems()

Actualmente, en mi código he intentado usar IEnumerable tanto como sea posible, pero no estoy seguro de si esta es la mejor práctica. Parecía correcto porque estaba devolviendo el tipo de datos más genérico sin dejar de ser descriptivo de lo que hace, pero tal vez esto no sea correcto.

ReyNestor
fuente
1
¿Es List <T> o IList <T> sobre lo que está preguntando? El título y la pregunta dicen cosas diferentes ...
Fredrik Mörk
Siempre que sea posible, la interfaz de usuario IEnumerable o Ilist instaead de tipo concreto.
Usman Masood
2
Devuelvo colecciones como List <T>. No veo la necesidad de devolver IEnumberable <T> ya que puede extraerlo de List <T>
Chuck Conway
posible duplicado de ienumerablet-as-return-type
nawfal

Respuestas:

44

Realmente depende de por qué está utilizando esa interfaz específica.

Por ejemplo, IList<T>tiene varios métodos que no están presentes en IEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

y Propiedades:

  • T this[int index] { get; set; }

Si necesita estos métodos de alguna manera, entonces regrese IList<T>.

Además, si el método que consume su IEnumerable<T>resultado espera un resultado IList<T>, evitará que CLR considere las conversiones necesarias, optimizando así el código compilado.

Jon Limjap
fuente
2
@Jon FDG recomienda usar Collection <T> o ReadOnlyCollection <T> como valor de retorno para los tipos de colección, vea mi respuesta.
Sam Saffron
No tiene claro lo que quiere decir en su última oración cuando dice "salvará el CLR". ¿Qué lo salvará, usando IEnumerable vs. IList? ¿Puedes aclarar eso?
PositiveGuy
1
@CoffeeAddict Tres años después de esta respuesta, creo que tienes razón: la última parte es vaga. Si un método que espera un IList <T> como parámetro obtiene un IEnumerable <T>, IEnumerable tiene que ser envuelto manualmente en un nuevo List <T> u otro implementador de IList <T>, y ese trabajo no lo hará el CLR para ti. Lo contrario: un método que espera que un IEnumerable <T> obtenga un IList <T>, puede tener que hacer un desempaquetado, pero en retrospectiva puede que no sea necesario porque IList <T> implementa IEnumerable <T>.
Jon Limjap
68

Las pautas de diseño de framework recomiendan usar la colección de clases cuando necesitas devolver una colección que el llamador puede modificar o ReadOnlyCollection para colecciones de solo lectura.

La razón por la que se prefiere esto a una simple IListes que IListno informa a la persona que llama si es de solo lectura o no.

Si devuelve un IEnumerable<T>en su lugar, ciertas operaciones pueden ser un poco más complicadas para la persona que llama. Además, ya no le dará a la persona que llama la flexibilidad de modificar la colección, algo que puede que desee o no.

Tenga en cuenta que LINQ contiene algunos trucos bajo la manga y optimizará ciertas llamadas según el tipo en el que se realizan. Entonces, por ejemplo, si realiza una County la colección subyacente es una Lista, NO recorrerá todos los elementos.

Personalmente, para un ORM probablemente me quedaría Collection<T>como mi valor de retorno.

Sam Saffron
fuente
12
Las Directrices para las colecciones contienen una lista más detallada de lo que se debe y no se debe hacer.
Chaquotay
26

En general, debe solicitar lo más genérico y devolver lo más específico que pueda. Entonces, si tiene un método que toma un parámetro, y solo necesita lo que está disponible en IEnumerable, entonces ese debería ser su tipo de parámetro. Si su método puede devolver un IList o un IEnumerable, prefiera devolver IList. Esto asegura que sea utilizable por la más amplia gama de consumidores.

Sea flexible en lo que necesita y explícito en lo que proporciona.

Mel
fuente
1
He llegado a la conclusión opuesta: que uno debe aceptar tipos específicos y devolver tipos generales. Sin embargo, puede tener razón en que los métodos de aplicación más amplia son mejores que los métodos más restringidos. Tendré que pensar más en esto.
Dave Cousineau
3
La justificación para aceptar tipos generales como entrada es que le permite trabajar con el rango más amplio de entrada posible para obtener la mayor reutilización posible de un componente. Por otro lado, como ya sabe exactamente qué tipo de objeto tiene a mano, no tiene mucho sentido enmascararlo.
Mel
1
Creo que estoy de acuerdo con tener parámetros más generales, pero ¿cuál es su razonamiento para devolver algo menos general?
Dave Cousineau
6
Bien, déjame intentarlo de otra manera. ¿Por qué tirarías información? Si solo le importa que el resultado sea IEnumerable <T>, ¿le duele de alguna manera saber que es un IList <T>? No, no es así. Puede ser información superflua en algunos casos, pero no le hace daño. Ahora en beneficio. Si devuelve una Lista o IList, puedo decir inmediatamente que la colección ya se ha recuperado, algo que no puedo saber con un IEnumerable. Esto puede ser o no información útil, pero una vez más, ¿por qué tiraría información? Si conoce información adicional sobre algo, transmítala.
Mel
En retrospectiva, me sorprende que nadie me haya llamado por eso. Ya se habrá recuperado un IEnumerable. Sin embargo, un IQueryable podría devolverse en un estado no enumerado. Entonces, quizás un mejor ejemplo sería devolver IQueryable vs IEnumerable. Devolver el IQueryable más específico permitiría una mayor composición, mientras que devolver IEnumerable forzaría la enumeración inmediata y disminuiría la componibilidad del método.
Mel
23

Eso depende...

Devolver el tipo menos derivado ( IEnumerable) le dejará el mayor margen de maniobra para cambiar la implementación subyacente en el futuro.

Devolver un tipo más derivado ( IList) proporciona a los usuarios de su API más operaciones sobre el resultado.

Siempre sugeriría devolver el tipo menos derivado que tenga todas las operaciones que sus usuarios van a necesitar ... así que, básicamente, primero debe eliminar las operaciones en el resultado que tienen sentido en el contexto de la API que está definiendo.

jerryjvl
fuente
buena respuesta genérica ya que también se aplica a otros métodos.
user420667
1
Sé que esta respuesta es muy antigua ... pero parece contradecir la documentación: "Si ni la interfaz IDictionary <TKey, TValue> ni la interfaz IList <T> cumplen con los requisitos de la colección requerida, derive la nueva clase de colección de la interfaz ICollection <T> en su lugar para mayor flexibilidad "Esto parece implicar que se debería preferir el tipo más derivado. ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK
11

Una cosa a considerar es que si está utilizando una declaración LINQ de ejecución diferida para generar su IEnumerable<T>, llamar .ToList()antes de regresar desde su método significa que sus elementos pueden repetirse dos veces: una vez para crear la Lista y una vez cuando la persona que llama se repite. , filtra o transforma su valor de retorno. Cuando sea práctico, me gusta evitar convertir los resultados de LINQ-to-Objects en una Lista o Diccionario concreto hasta que sea necesario. Si la persona que llama necesita una lista, es una simple llamada de método fácil; no necesito tomar esa decisión por ellos, y eso hace que mi código sea un poco más eficiente en los casos en que la persona que llama solo está haciendo un foreach.

Joel Mueller
fuente
@Joel Mueller, normalmente los llamaría ToList () de todos modos. Generalmente no me gusta exponer IQueryable al resto de mis proyectos.
KingNestor
4
Me refería más a LINQ-to-Objects, donde IQueryable generalmente no entra en escena. Cuando hay una base de datos involucrada, ToList () se vuelve más necesario, porque de lo contrario corre el riesgo de cerrar su conexión antes de iterar, lo que no funciona muy bien. Sin embargo, cuando eso no es un problema, es bastante fácil exponer IQueryable como un IEnumerable sin forzar una iteración adicional cuando desea ocultar el IQueryable.
Joel Mueller
9

List<T>ofrece al código de llamada muchas más funciones, como modificar el objeto devuelto y acceder por índice. Entonces, la pregunta se reduce a: en el caso de uso específico de su aplicación, ¿DESEA admitir dichos usos (¡presumiblemente devolviendo una colección recién construida!), Para la conveniencia de la persona que llama, o desea velocidad para el caso simple cuando todos los lo que necesita la persona que llama es recorrer la colección y puede devolver de forma segura una referencia a una colección subyacente real sin temer que esto la cambie por error, etc.

Solo usted puede responder esta pregunta, y solo si comprende bien lo que las personas que llaman querrán hacer con el valor de retorno y qué tan importante es el rendimiento aquí (qué tan grandes son las colecciones que estaría copiando, qué tan probable es que esto sea un cuello de botella, etc).

Alex Martelli
fuente
"devolver de forma segura una referencia a una colección subyacente real sin temer que esto la cambie por error" - Incluso si devuelve IEnumerable <T>, ¿no podrían simplemente devolverlo a List <T> y cambiarlo?
Kobi
No todos los IEnumarable <T> son también List <T>. Si el objeto devuelto no es de un tipo que hereda de List <T> o implementa IList <T>, esto resultaría en una InvalidCastException.
lowglider
2
List <T> tiene el problema de que lo bloquea en una implementación particular Collection <T> o ReadOnlyCollection <T> son preferidos
Sam Saffron
4

Creo que puedes usar cualquiera, pero cada uno tiene un uso. Básicamente lo Listes, IEnumerablepero tiene la funcionalidad de contar, agregar elemento, eliminar elemento

IEnumerable no es eficiente para contar elementos

Si la colección está destinada a ser de solo lectura, o la modificación de la colección está controlada por el, Parententonces devolver un IListsolo para Countno es una buena idea.

En Linq, hay un Count()método de extensión en el IEnumerable<T>que dentro del CLR se accederá a .Countsi el tipo subyacente es de IList, por lo que la diferencia de rendimiento es insignificante.

En general, creo (opinión) que es una mejor práctica devolver IEnumerable donde sea posible, si necesita hacer adiciones, agregue estos métodos a la clase principal; de lo contrario, el consumidor administra la colección dentro del Modelo que viola los principios, por ejemplo, manufacturer.Models.Add(model)viola la ley de demeter. Por supuesto, estas son solo pautas y no reglas estrictas y rápidas, pero hasta que tenga una comprensión completa de la aplicabilidad, es mejor seguir ciegamente que no seguir en absoluto.

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(Nota: si usa nNHibernate, es posible que deba mapear a IList privada usando diferentes accesadores).

SO Usuario
fuente
2

No es tan simple cuando se habla de valores de retorno en lugar de parámetros de entrada. Cuando se trata de un parámetro de entrada, sabe exactamente lo que debe hacer. Entonces, si necesita poder iterar sobre la colección, toma un IEnumberable mientras que si necesita agregar o eliminar, toma un IList.

En el caso de un valor de retorno, es más difícil. ¿Qué espera la persona que llama? Si devuelve un IEnumerable, entonces él no sabrá a priori que puede convertirlo en un IList. Pero, si devuelve un IList, sabrá que puede iterar sobre él. Por lo tanto, debe tener en cuenta lo que hará la persona que llama con los datos. La funcionalidad que la persona que llama necesita / espera es lo que debe regir al tomar la decisión sobre qué devolver.

JP Alioto
fuente
0

como todos han dicho, depende, si no desea Agregar / Eliminar funcionalidad en la capa de llamada, votaré por IEnumerable, ya que solo proporciona iteración y funcionalidad básica que, en perspectiva de diseño, me gusta. Volviendo a IList, mis votos siempre son de nuevo, pero es principalmente lo que te gusta y lo que no. en términos de rendimiento, creo que son más de lo mismo.

Usman Masood
fuente
0

Si no cuenta en su código externo, siempre es mejor devolver IEnumerable, porque luego puede cambiar su implementación (sin impacto de código externo), por ejemplo, para generar lógica de iterador y conservar recursos de memoria (muy buena característica de lenguaje por cierto ).

Sin embargo, si necesita un recuento de elementos, no olvide que hay otra capa entre IEnumerable e IList - ICollection .

árbitro
fuente
0

Puede que esté un poco fuera de lugar, ya que nadie más lo sugirió hasta ahora, pero ¿por qué no devuelves un (I)Collection<T>?

Por lo que recuerdo, Collection<T>fue el tipo de retorno preferido List<T>porque abstrae la implementación. Todos implementan IEnumerable, pero eso me suena un poco demasiado bajo para el trabajo.

Tiberiu Ana
fuente
0

Creo que puedes usar cualquiera, pero cada uno tiene un uso. Básicamente lo Listes, IEnumerablepero tiene la funcionalidad de conteo, agregar elemento, eliminar elemento

IEnumerable no es eficiente para contar elementos o para obtener un elemento específico en la colección.

List es una colección ideal para encontrar elementos específicos, fácil de agregar o eliminar.

En general, trato de usarlo Listsiempre que sea posible, ya que esto me da más flexibilidad.

Usar en List<FooBar> getRecentItems() lugar de IList<FooBar> GetRecentItems()

Stuart
fuente
0

Creo que la regla general es usar la clase más específica para regresar, para evitar hacer trabajos innecesarios y darle más opciones a la persona que llama.

Dicho esto, creo que es más importante considerar el código que está escribiendo frente a usted que el código que escribirá el próximo (dentro de lo razonable). Esto se debe a que puede hacer suposiciones sobre el código que ya existe.

Recuerde que moverse hacia ARRIBA a una colección desde IEnumerable en una interfaz funcionará, moverse hacia abajo a IEnumerable desde una colección romperá el código existente.

Si todas estas opiniones parecen estar en conflicto, es porque la decisión es subjetiva.

Sprague
fuente
0

TL; DR; - resumen

  • Si desarrolla software interno, utilice el tipo específico (Me gusta List) para los valores de retorno y el tipo más genérico para los parámetros de entrada, incluso en el caso de colecciones.
  • Si un método es parte de la API pública de una biblioteca redistribuible, use interfaces en lugar de tipos de colección concretos para introducir valores de retorno y parámetros de entrada.
  • Si un método devuelve una colección de solo lectura, muéstrelo usando IReadOnlyListo IReadOnlyCollectioncomo el tipo de valor de retorno.

Más

Ali Bayat
fuente