Retornando 'IList' vs 'ICollection' vs 'Collection'

119

Estou confuso sobre qual tipo de coleção devo retornar dos meus métodos e propriedades da API pública.

As coleções que eu tenho em mente são IList, ICollectione Collection.

O retorno de um desses tipos sempre é preferido em relação aos outros, ou depende da situação específica?

Rocky Singh
fonte

Respostas:

71

Geralmente, você deve retornar um tipo o mais geral possível, ou seja, aquele que conheça apenas os dados retornados que o consumidor precisa usar. Dessa forma, você tem maior liberdade para alterar a implementação da API, sem quebrar o código que a está usando.

Considere também a IEnumerable<T>interface como tipo de retorno. Se o resultado for apenas iterado, o consumidor não precisará mais do que isso.

Guffa
fonte
25
Mas considere também ICollection <T>, que estende IEnumerable <T> para fornecer utilitários adicionais, incluindo uma propriedade Count. Isso pode ser útil porque você pode determinar o comprimento da sequência sem percorrê-la várias vezes.
Retrodrone
11
@retrodrone: Na verdade, a implementação do Countmétodo verifica o tipo real da coleção, portanto ele usará as propriedades Lengthou Countpara alguns tipos conhecidos, como matrizes e ICollectionmesmo quando você o fornecer como IEnumerable.
Guffa
8
@ Guffa: Pode valer a pena notar que, se uma classe Thing<T>implementa, IList<T>mas não a não genérica ICollection, chamar IEnumerable<Cat>.Count()um Thing<Cat>seria rápido, mas chamar IEnumerable<Animal>.Count()seria lento (já que o método de extensão procuraria, e não encontraria, uma implementação de ICollection<Cat>) Se a classe implementasse o não genérico ICollection, no entanto, IEnumerable<Animal>.Countencontraria e usaria isso.
supercat
8
Discordo. É melhor retornar o tipo mais rico que você possui para que o cliente possa usá-lo diretamente, se é isso que eles desejam. Se você possui uma Lista <>, por que retornar um IEnuerable <> apenas para forçar o chamador a chamar ToList () desnecessariamente?
Zumalifeguard 17/05
140

ICollection<T>é uma interface que expõe semântica de recolha, tais como Add(), Remove()e Count.

Collection<T>é uma implementação concreta da ICollection<T>interface.

IList<T>é essencialmente um ICollection<T>com acesso baseado em ordem aleatória.

Nesse caso, você deve decidir se seus resultados requerem ou não semânticas de lista, como indexação baseada em pedidos (depois use IList<T>) ou se você só precisa retornar uma "bolsa" não ordenada de resultados (então use ICollection<T>).

cordialgerm
fonte
5
Collection<T>implementa IList<T>e não apenas ICollection<T>.
código é o seguinte
56
Todas as coleções em .Net são ordenadas, porque IEnumerable<T>são ordenadas. O que distingue IList<T>a partir de ICollection<T>é que ele oferece aos membros a trabalhar com índices. Por exemplo list[5], funciona, mas collection[5]não compila.
svick
5
Também discordo que as coleções ordenadas devem ser implementadas IList<T>. Há muitas coleções ordenadas que não. IList<T>trata-se de acesso indexado rápido.
CodesInChaos
4
@yoyo As classes e interfaces da coleção .net são simplesmente mal projetadas e mal nomeadas. Eu não pensaria muito sobre por que eles os nomearam assim.
CodesInChaos
6
@svick: todas as coleções são encomendadas porque IEnumerable<T>são encomendadas? Take HashSet<T>- ele implementa, IEnumerable<T>mas claramente não está ordenado. Ou seja, você não tem influência na ordem dos itens e essa ordem pode mudar a qualquer momento, se a tabela de hash interna for reorganizada.
Olivier Jacot-Descombes
61

A principal diferença entre IList<T>e ICollection<T>é que IList<T>permite acessar elementos por meio de um índice. IList<T>descreve tipos do tipo matriz. Os elementos em um ICollection<T>só podem ser acessados ​​por enumeração. Ambos permitem a inserção e exclusão de elementos.

Se você precisar enumerar apenas uma coleção, IEnumerable<T>é preferível. Tem duas vantagens sobre as outras:

  1. Ele não permite alterações na coleção (mas não nos elementos, se eles são do tipo de referência).

  2. Ele permite a maior variedade possível de fontes, incluindo enumerações que são geradas algoritmicamente e não são coleções.

Collection<T>é uma classe base que é útil principalmente para implementadores de coleções. Se você expô-lo em interfaces (APIs), muitas coleções úteis não derivadas serão excluídas.


Uma desvantagem IList<T>é que as matrizes a implementam, mas não permitem adicionar ou remover itens (ou seja, você não pode alterar o comprimento da matriz). Uma exceção será lançada se você chamar IList<T>.Add(item)uma matriz. A situação é um pouco neutralizada, pois IList<T>possui uma propriedade booleana IsReadOnlyque você pode verificar antes de tentar fazer isso. Mas, aos meus olhos, isso ainda é uma falha de design na biblioteca . Portanto, eu uso List<T>diretamente, quando a possibilidade de adicionar ou remover itens é necessária.

Olivier Jacot-Descombes
fonte
Análise de Código (e FxCop) informa que ao retornar uma coleção genérica de concreto, uma das seguintes opções deve ser devolvido: Collection, ReadOnlyCollectionou KeyedCollection. E isso Listnão deve ser devolvido.
DavidRR
@ DavididRR: Muitas vezes, é útil passar uma lista para um método que precisa adicionar ou remover itens. Se você digitar o parâmetro como IList<T>, não há como garantir que o método não seja chamado com uma matriz como parâmetro.
Olivier Jacot-Descombes
7

IList<T>é a interface base para todas as listas genéricas. Por ser uma coleção ordenada, a implementação pode decidir sobre a ordem, variando de ordem classificada a ordem de inserção. Além disso, Ilistpossui a propriedade Item que permite que os métodos leiam e editem entradas na lista com base em seu índice. Isso possibilita inserir, remover um valor na / da lista em um índice de posição.

Além disso IList<T> : ICollection<T>, todos os métodos de ICollection<T>também estão disponíveis aqui para implementação.

ICollection<T>é a interface base para todas as coleções genéricas. Ele define tamanho, enumeradores e métodos de sincronização. Você pode adicionar ou remover um item em uma coleção, mas não pode escolher em qual posição ele ocorre devido à ausência da propriedade de índice.

Collection<T>fornece uma implementação para IList<T>, IListe IReadOnlyList<T>.

Se você usar um tipo de interface mais restrito, como em ICollection<T>vez de IList<T>, protege seu código contra as alterações. Se você usar um tipo de interface mais amplo, como IList<T>, você corre o risco de quebrar as alterações de código.

Citando de uma fonte ,

ICollection, ICollection<T>: Você deseja modificar a coleção ou se importa com seu tamanho. IList, IList<T>: Você quer modificar a coleção e você se preocupa com a ordenação e / ou posicionamento dos elementos na coleção.

NullReference
fonte
5

O retorno de um tipo de interface é mais geral, portanto (sem informações adicionais sobre seu caso de uso específico), eu me inclinaria para isso. Se você deseja expor o suporte à indexação, escolha IList<T>, caso contrário, ICollection<T>será suficiente. Por fim, se você deseja indicar que os tipos retornados são somente leitura, escolha IEnumerable<T>.

E, caso você não tenha lido antes, Brad Abrams e Krzysztof Cwalina escreveram um ótimo livro intitulado "Diretrizes de design de estrutura: convenções, expressões idiomáticas e padrões para bibliotecas .NET reutilizáveis" (você pode baixar um resumo aqui ).

Alan
fonte
IEnumerable<T>é somente leitura? Claro, mas ao executá-lo novamente em um contexto de repositório, mesmo depois de executar o ToList, o thread não é seguro. O problema é quando a fonte de dados original obtém o GC e o IEnumarble.ToList () não funciona em um contexto multithread. Ele funciona de maneira linear, porque o GC é chamado depois que você faz o trabalho, mas asyncagora usa os dias de 4.5+ IEnumarbale está se tornando uma dor de bola.
Piotr Kula
-3

Existem alguns assuntos que vêm dessa pergunta:

  • interfaces versus classes
  • qual classe específica, de várias classes iguais, coleção, lista, matriz?
  • Classes comuns versus coleções de subitens ("genéricos")

Convém destacar que é uma API orientada a objetos

interfaces versus classes

Se você não tem muita experiência com interfaces, recomendo manter as aulas. Eu vejo muitas vezes os desenvolvedores pulando para interfaces, mesmo que não seja necessário.

E, finalize com um design de interface ruim, em vez de um bom design de classe, que, a propósito, pode eventualmente ser migrado para um bom design de interface ...

Você verá muitas interfaces na API, mas não se apresse, se não precisar.

Você acabará aprendendo como aplicar interfaces ao seu código.

qual classe específica, de várias classes iguais, coleção, lista, matriz?

Existem várias classes em c # (dotnet) que podem ser trocadas. Como já mencionado, se você precisar de algo de uma classe mais específica, como "CanBeSortedClass", torne explícito na sua API.

O usuário da API realmente precisa saber que sua classe pode ser classificada ou aplicar algum formato aos elementos? Em seguida, use "CanBeSortedClass" ou "ElementsCanBePaintedClass", caso contrário, use "GenericBrandClass".

Caso contrário, use uma classe mais geral.

Classes de coleção comuns versus coleções de subitens ("genéricos")

Você verá que existem classes que contêm outros elementos e pode especificar que todos os elementos devem ser de um tipo específico.

Coleções genéricas são aquelas classes que você pode usar a mesma coleção, para vários aplicativos de código, sem precisar criar uma nova coleção, para cada novo tipo de subitem, como este: Coleção .

Seu usuário da API precisará de um tipo muito específico, o mesmo para todos os elementos?

Use algo parecido List<WashingtonApple>.

Seu usuário da API precisará de vários tipos relacionados?

Expor List<Fruit>para o seu API, e usar List<Orange> List<Banana>, List<Strawberry>internamente, onde Orange, Bananae Strawberrysão descendentes de Fruit.

Seu usuário da API precisará de uma coleção de tipos genéricos?

Use List, onde todos os itens são object.

Felicidades.

umlcat
fonte
7
"Se você não tem muita experiência com interfaces, recomendo manter as aulas" Este não é um bom conselho. É como dizer se há algo que você não sabe, apenas fique longe disso. As interfaces são extremamente poderosas e corroboram o conceito de "Programa para abstrações versus concreções". Elas também são bastante poderosas para realizar testes de unidade devido à capacidade de trocar tipos por zombaria. Existem várias outras ótimas razões para usar o Interfaces além desses comentários, então, por todos os meios, explore-os.
Atletway
@atconway Eu costumo usar interfaces, mas, como muitos conceitos, é uma ferramenta poderosa, pode ser fácil de usar. E, às vezes, o desenvolvedor pode não precisar. Minha sugestão não foi "nunca fazer interfaces", minha sugestão foi "nesse caso, você pode pular as interfaces. Aprenda sobre isso e você poderá obter o melhor delas posteriormente". Felicidades.
umlcat
1
Eu recomendo sempre programar para uma interface. Isso ajuda a manter o código fracamente acoplado.
precisa saber é o seguinte
@ mac10688 Minha resposta anterior (atconway) também se aplica ao seu comentário.
umlcat