Devolver 'IList' vs 'ICollection' vs 'Collection'

Respuestas:

71

Generalmente, debe devolver un tipo que sea lo más general posible, es decir, uno que conozca lo suficiente de los datos devueltos que el consumidor necesita usar. De esa forma tienes mayor libertad para cambiar la implementación de la API, sin romper el código que la está usando.

Considere también la IEnumerable<T>interfaz como tipo de retorno. Si el resultado solo se va a iterar, el consumidor no necesita más que eso.

Guffa
fuente
25
Pero también considere ICollection <T> que extiende IEnumerable <T> para proporcionar utilidades adicionales, incluida una propiedad Count. Esto puede ser útil porque puede determinar la longitud de la secuencia sin atravesar la secuencia varias veces.
retrodrone
11
@retrodrone: en realidad, la implementación del Countmétodo verifica el tipo real de la colección, por lo que usará las propiedades Lengtho Countpara algunos tipos conocidos como matrices e ICollectionincluso cuando lo proporcione como un archivo IEnumerable.
Guffa
8
@Guffa: puede valer la pena tener en cuenta que si una clase Thing<T>implementa IList<T>pero no la no genérica ICollection, la llamada IEnumerable<Cat>.Count()a Thing<Cat>sería rápido, pero la llamada IEnumerable<Animal>.Count()sería lenta (ya que el método de extensión buscaría, y no encontraría, una implementación de ICollection<Cat>). ICollectionSin embargo, si la clase implementa lo no genérico , IEnumerable<Animal>.Countlo encontraría y usaría.
supercat
8
Estoy en desacuerdo. Es mejor devolver el tipo más rico que tenga para que el cliente pueda usarlo directamente si eso es lo que quiere. Si tiene una List <>, ¿por qué devolver un IEnuerable <> solo para obligar a la persona que llama a llamar a ToList () innecesariamente?
zumalifeguard
140

ICollection<T>es una interfaz que expone la semántica de recogida, tales como Add(), Remove(), y Count.

Collection<T>es una implementación concreta de la ICollection<T>interfaz.

IList<T>es esencialmente un ICollection<T>acceso basado en orden aleatorio.

En este caso, debe decidir si sus resultados requieren o no semántica de lista, como indexación basada en orden (luego usar IList<T>) o si solo necesita devolver una "bolsa" desordenada de resultados (luego usar ICollection<T>).

cordialgerm
fuente
5
Collection<T>implementos IList<T>y no solo ICollection<T>.
CodesInChaos
56
Todas las colecciones en .Net están ordenadas, porque IEnumerable<T>está ordenado. Lo que distingue IList<T>a partir ICollection<T>es que ofrece a los miembros a trabajar con índices. Por ejemplo list[5], funciona, pero collection[5]no se compila.
svick
5
Tampoco estoy de acuerdo con que las colecciones ordenadas se implementen IList<T>. Hay muchas colecciones ordenadas que no lo hacen. IList<T>se trata de un acceso indexado rápido.
CodesInChaos
4
@yoyo Las clases e interfaces de la colección .net simplemente están mal diseñadas y mal nombradas. No pensaría demasiado en por qué los nombraron así.
CodesInChaos
6
@svick: ¿Están todas las colecciones ordenadas porque IEnumerable<T>están ordenadas? Tome HashSet<T>: implementa IEnumerable<T>pero claramente no está ordenado. Es decir, no tiene ninguna influencia en el orden de los elementos y este orden podría cambiar en cualquier momento, si se reorganiza la tabla hash interna.
Olivier Jacot-Descombes
61

La principal diferencia entre IList<T>y ICollection<T>es que le IList<T>permite acceder a elementos a través de un índice. IList<T>describe tipos similares a matrices. ICollection<T>Solo se puede acceder a los elementos de una enumeración. Ambos permiten la inserción y eliminación de elementos.

Si solo necesita enumerar una colección, entonces IEnumerable<T>es preferible. Tiene dos ventajas sobre las demás:

  1. No permite cambios en la colección (pero no en los elementos, si son del tipo de referencia).

  2. Permite la mayor variedad posible de fuentes, incluidas las enumeraciones que se generan algorítmicamente y no son colecciones en absoluto.

Collection<T>es una clase base que es principalmente útil para implementadores de colecciones. Si lo expone en interfaces (API), se excluirán muchas colecciones útiles que no se deriven de él.


Una desventaja de IList<T>es que los arreglos lo implementan pero no le permiten agregar o quitar elementos (es decir, no puede cambiar la longitud del arreglo). Se lanzará una excepción si llama IList<T>.Add(item)a una matriz. La situación está algo desactivada, ya que IList<T>tiene una propiedad booleana IsReadOnlyque puede verificar antes de intentar hacerlo. Pero en mi opinión, esto sigue siendo un defecto de diseño en la biblioteca . Por lo tanto, lo uso List<T>directamente, cuando se requiere la posibilidad de agregar o quitar elementos.

Olivier Jacot-Descombes
fuente
Análisis de código (y FxCop) aconseja que al devolver una colección genérica concreta, una de las siguientes deben ser devueltos: Collection, ReadOnlyCollectiono KeyedCollection. Y eso Listno debe devolverse.
DavidRR
@DavidRR: A menudo es útil pasar una lista a un método que tiene que agregar o eliminar elementos. Si escribe el parámetro como IList<T>, no hay forma de asegurarse de que el método no se llame con una matriz como parámetro.
Olivier Jacot-Descombes
7

IList<T>es la interfaz base para todas las listas genéricas. Dado que es una colección ordenada, la implementación puede decidir sobre el orden, desde el orden clasificado hasta el orden de inserción. Además, Ilisttiene la propiedad Item que permite a los métodos leer y editar entradas en la lista según su índice. Esto hace posible insertar, eliminar un valor en / de la lista en un índice de posición.

Además IList<T> : ICollection<T>, todos los métodos de ICollection<T>también están disponibles aquí para su implementación.

ICollection<T>es la interfaz base para todas las colecciones genéricas. Define tamaño, enumeradores y métodos de sincronización. Puede agregar o eliminar un elemento en una colección, pero no puede elegir en qué posición ocurre debido a la ausencia de la propiedad del índice.

Collection<T>proporciona una implementación para IList<T>, IListy IReadOnlyList<T>.

Si usa un tipo de interfaz más estrecha, como en ICollection<T>lugar de IList<T>, protege su código contra cambios importantes. Si usa un tipo de interfaz más amplio como IList<T>, corre más peligro de romper los cambios de código.

Citando de una fuente ,

ICollection, ICollection<T>: Quiere modificar la colección o le importa su tamaño. IList, IList<T>: Quiere modificar la colección y le importa el orden y / o posicionamiento de los elementos en la colección.

NullReference
fuente
5

Devolver un tipo de interfaz es más general, por lo que (sin más información sobre su caso de uso específico) me inclinaría por eso. Si desea exponer el soporte de indexación, elija IList<T>, de lo contrario ICollection<T>será suficiente. Por último, si desea indicar que los tipos devueltos son de solo lectura, elija IEnumerable<T>.

Y, en caso de que no lo haya leído antes, Brad Abrams y Krzysztof Cwalina escribieron un gran libro titulado "Pautas de diseño de marcos: convenciones, modismos y patrones para bibliotecas .NET reutilizables" (puede descargar un resumen desde aquí ).

Alan
fuente
IEnumerable<T>es de solo lectura? Claro, pero al volver a sintonizarlo en un contexto de repositorio, incluso después de ejecutar ToList no es seguro para subprocesos. El problema es que cuando la fuente de datos original obtiene GC, IEnumarble.ToList () no funciona en un contexto de subprocesos múltiples. Funciona de forma lineal porque se llama a GC después de hacer el trabajo, pero usarlo asyncahora en 4.5+ IEnumarbale se está convirtiendo en un dolor de cabeza.
Piotr Kula
-3

Hay algunos temas que surgen de esta pregunta:

  • interfaces versus clases
  • ¿Qué clase específica, de varias clases similares, colección, lista, matriz?
  • Clases comunes frente a colecciones de subelementos ("genéricos")

Es posible que desee resaltar que es una API orientada a objetos

interfaces versus clases

Si no tienes mucha experiencia con las interfaces, te recomiendo que sigas las clases. Veo muchas veces a los desarrolladores saltar a las interfaces, incluso si no es necesario.

Y, terminar haciendo un diseño de interfaz deficiente, en lugar de un buen diseño de clase, que, por cierto, eventualmente se puede migrar a un buen diseño de interfaz ...

Verá muchas interfaces en la API, pero no se apresure a buscarlas si no las necesita.

Eventualmente aprenderá a aplicar interfaces a su código.

¿Qué clase específica, de varias clases similares, colección, lista, matriz?

Hay varias clases en c # (dotnet) que se pueden intercambiar. Como ya se mencionó, si necesita algo de una clase más específica, como "CanBeSortedClass", hágalo explícito en su API.

¿Su usuario de API realmente necesita saber que su clase se puede ordenar o aplicar algún formato a los elementos? Luego use "CanBeSortedClass" o "ElementsCanBePaintedClass", de lo contrario use "GenericBrandClass".

De lo contrario, use una clase más general.

Clases de colección comunes versus colecciones de subelementos ("genéricos")

Verá que hay clases que contienen otros elementos y puede especificar que todos los elementos deben ser de un tipo específico.

Las colecciones genéricas son aquellas clases que puede usar la misma colección, para varias aplicaciones de código, sin tener que crear una nueva colección, para cada nuevo tipo de subelemento, como este: Colección .

¿Su usuario de API va a necesitar un tipo muy específico, igual para todos los elementos?

Usa algo como List<WashingtonApple>.

¿Su usuario de API necesitará varios tipos relacionados?

Exponer List<Fruit>para su API, y utilizar List<Orange> List<Banana>, List<Strawberry>internamente, en donde Orange, Bananay Strawberryson descendientes de Fruit.

¿Su usuario de API necesitará una colección de tipos genéricos?

Use List, donde están todos los artículos object.

Salud.

umlcat
fuente
7
"Si no tienes mucha experiencia con interfaces, te recomiendo que sigas las clases" Este no es un buen consejo. Eso es como decir que si hay algo que no sabe, simplemente manténgase alejado de ello. Las interfaces son extremadamente poderosas y respaldan el concepto de "Programa para abstracciones versus concreciones". También son bastante poderosas para realizar pruebas unitarias debido a la capacidad de intercambiar tipos a través de simulaciones. Hay un montón de otras buenas razones para usar interfaces más allá de estos comentarios, así que por favor explórelas.
atconway
@atconway Yo uso interfaces con regularidad, pero, como muchos conceptos, es una herramienta poderosa, puede ser fácil de mezclar. Y, a veces, es posible que el desarrollador no lo necesite. Mi sugerencia no fue "nunca nunca interfaces", mi sugerencia fue "en este caso, puede omitir las interfaces. Aprenda sobre esto, y puede obtener lo mejor de ellas, más tarde". Salud.
umlcat
1
Recomiendo programar siempre en una interfaz. Ayuda a mantener el código acoplado libremente.
mac10688
@ mac10688 Mi respuesta anterior (atconway) también se aplica a su comentario.
umlcat