Coleção <T> versus Lista <T>, o que você deve usar em suas interfaces?

162

O código se parece abaixo:

namespace Test
{
    public interface IMyClass
    {
        List<IMyClass> GetList();
    }

    public class MyClass : IMyClass
    {
        public List<IMyClass> GetList()
        {
            return new List<IMyClass>();
        }
    }
}

Quando executo a análise de código, recebo a seguinte recomendação.

Aviso 3 CA1002: Microsoft.Design: altere 'List' em 'IMyClass.GetList ()' para usar Collection, ReadOnlyCollection ou KeyedCollection

Como devo corrigir isso e quais são as boas práticas aqui?

bovium
fonte

Respostas:

234

Para responder à parte "por que" da pergunta e por que não List<T>, os motivos são a prova do futuro e a simplicidade da API.

À prova de futuro

List<T>não foi projetado para ser facilmente extensível subclassificando-o; Ele foi projetado para ser rápido para implementações internas. Você notará que os métodos nele não são virtuais e, portanto, não podem ser substituídos, e não há ganchos em suas operações Add/ Insert/ Remove.

Isso significa que, se você precisar alterar o comportamento da coleção no futuro (por exemplo, para rejeitar objetos nulos que as pessoas tentam adicionar ou executar trabalhos adicionais quando isso acontecer, como atualizar o estado da classe), será necessário alterar o tipo da coleção, você retorna para uma subclasse que pode ser quebrada, o que será uma mudança na interface (é claro que mudar a semântica de coisas como não permitir nulo também pode ser uma alteração na interface, mas coisas como atualizar o estado da classe interna não seriam).

Portanto, retornando uma classe que possa ser facilmente subclassificada Collection<T>ou uma interface como IList<T>, ICollection<T>ou IEnumerable<T>você pode alterar sua implementação interna para ser um tipo de coleção diferente para atender às suas necessidades, sem quebrar o código dos consumidores, pois ainda pode ser retornado como o tipo que eles estão esperando.

Simplicidade da API

List<T>contém uma série de operações úteis, tais como BinarySearch, Sorte assim por diante. No entanto, se esta é uma coleção que você está expondo, é provável que você controle a semântica da lista, e não os consumidores. Portanto, embora sua classe internamente precise dessas operações, é muito improvável que os consumidores da sua classe desejem (ou até devam) chamá-las.

Dessa forma, ao oferecer uma classe ou interface de coleção mais simples, você reduz o número de membros que os usuários da sua API veem e facilita o uso deles.

Greg Beech
fonte
1
Esta resposta está morta. Outra boa leitura sobre o assunto pode ser encontrada aqui: blogs.msdn.com/fxcop/archive/2006/04/27/…
senfo
7
Vejo seus primeiros pontos, mas não sei se concordo com sua parte de simplicidade da API.
Boris Callens 5/03/09
stackoverflow.com/a/398988/2632991 Aqui também está uma boa publicação, sobre qual é a diferença entre Coleções e Listas.
El Mac
2
blogs.msdn.com/fxcop/archive/2006/04/27/… -> Está morto. Temos informações mais recentes para isso?
Machado
50

Eu pessoalmente declararia que ele retornaria uma interface em vez de uma coleção concreta. Se você realmente deseja acesso à lista, use IList<T>. Caso contrário, considere ICollection<T>e IEnumerable<T>.

Jon Skeet
fonte
1
O IList estenderia a interface ICollection?
bovium 7/11/08
8
@ Jon: Eu sei que isso é antigo, mas você pode comentar o que Krzysztof diz em blogs.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx ? Especificamente, seu comentário, We recommend using Collection<T>, ReadOnlyCollection<T>, or KeyedCollection<TKey,TItem> for outputs and properties and interfaces IEnumerable<T>, ICollection<T>, IList<T> for inputs. CA1002 , parece concordar com os comentários de Krzysztof. Não consigo imaginar por que uma coleção concreta seria recomendada em vez de uma interface e por que a distinção entre entradas / saídas.
Nelson Rothermel
3
@ Nelson: É raro você querer exigir que os chamadores passem em uma lista imutável, mas é razoável retornar uma para que eles saibam que é definitivamente imutável. Não tenho certeza sobre as outras coleções. Seria bom ter mais detalhes.
Jon Skeet
2
Não é para um caso específico. Obviamente, em geral ReadOnlyCollection<T>, não faria sentido para uma entrada. Da mesma forma, IList<T>como uma entrada diz: "Preciso de Sort () ou de algum outro membro que um IList tenha", o que não faz sentido para uma saída. Mas o que eu quis dizer foi: por que ICollection<T>seria recomendado como entrada e Collection<T>como saída. Por que não usar ICollection<T>como saída também como você sugeriu?
Nelson Rothermel
9
Estou pensando que tem a ver com a ambiguidade. Collection<T>e ReadOnlyCollection<T>ambos derivam de ICollection<T>(isto é, não há IReadOnlyCollection<T>). Se você retornar a interface, não é óbvio qual é e se pode ser modificado. De qualquer forma, obrigado pela sua contribuição. Esta foi uma boa verificação de sanidade para mim.
Nelson Rothermel
3

Acho que ninguém respondeu à parte "por que" ainda ... então aqui vai. A razão pela qual "você" deve "usar um em Collection<T>vez de a List<T>é porque, se você expõe a List<T>, qualquer pessoa que obtém acesso ao seu objeto pode modificar os itens da lista. Considerando que Collection<T>deve indicar que você está criando seus próprios métodos "Adicionar", "Remover", etc.

Você provavelmente não precisa se preocupar com isso, porque provavelmente está codificando a interface apenas para si (ou talvez para alguns colegas). Aqui está outro exemplo que pode fazer sentido.

Se você tiver uma matriz pública, ex:

public int[] MyIntegers { get; }

Você pensaria isso porque existe apenas um acessador "get" que ninguém pode mexer com os valores, mas isso não é verdade. Qualquer um pode alterar os valores lá dentro, assim:

someObject.MyIngegers[3] = 12345;

Pessoalmente, eu usaria apenas List<T>na maioria dos casos. Mas se você estiver projetando uma biblioteca de classes que irá distribuir para desenvolvedores aleatórios e precisar confiar no estado dos objetos ... então você desejará criar sua própria coleção e bloqueá-la a partir daí: )

Timothy Khouri
fonte
"Se você retornar a Lista <T> para o código do cliente, nunca poderá receber notificações quando o código do cliente modificar a coleção." - FX COP ... Ver também " msdn.microsoft.com/en-us/library/0fss9skc.aspx " ... wow, parece que eu não estou fora da base depois de tudo :)
Timothy Khouri
Uau - anos depois, vejo que o link que colei no comentário acima tinha uma citação no final, por isso não funcionou ... msdn.microsoft.com/en-us/library/0fss9skc.aspx
Timothy Khouri
2

É principalmente abstrair suas próprias implementações, em vez de expor o objeto List a ser manipulado diretamente.

Não é uma boa prática permitir que outros objetos (ou pessoas) modifiquem diretamente o estado dos seus objetos. Pense em getters / setters de propriedades.

Coleção -> Para coleção normal
ReadOnlyCollection -> Para coleções que não devem ser modificadas
KeyedCollection -> Quando você deseja dicionários.

Como corrigi-lo depende do que você deseja que sua classe faça e da finalidade do método GetList (). Você pode elaborar?

chakrit
fonte
1
Mas Collection <T> e KeyedCollection <,> também não são somente leitura.
Nawfal
@nawfal E onde eu disse que é?
chakrit
1
Você declara não permitir que outros objetos (ou pessoas) modifiquem diretamente o estado de seus objetos e lista 3 tipos de coleção. Exceto ReadOnlyCollectionos outros dois não obedece.
Nawfal 9/07
Isso se refere a "boas práticas". Contexto adequado, por favor. A lista abaixo simplesmente indica o requisito básico de tais tipos, pois o OP deseja entender o aviso. Depois, pergunto a ele sobre o propósito de GetList()poder ajudá-lo mais corretamente.
chakrit
1
Ok, bem, eu entendo essa parte. Mas ainda assim a parte do raciocínio não está correta, de acordo comigo. Você diz que Collection<T>ajuda a abstrair a implementação interna e impede a manipulação direta da lista interna. Quão? Collection<T>é apenas um wrapper, operando na mesma instância passada. É uma coleção dinâmica destinada à herança, nada mais (a resposta de Greg é mais relevante aqui).
Nawfal 9/07
1

Nesse tipo de caso, geralmente tento expor a menor quantidade de implementação necessária. Se os consumidores não precisarem saber que você está realmente usando uma lista, não será necessário retornar uma lista. Ao retornar como a Microsoft sugere uma coleção, você oculta o fato de estar usando uma lista dos consumidores de sua classe e os isola de uma alteração interna.

Harald Scheirich
fonte
1

Algo a acrescentar, embora tenha sido um longo tempo desde que isso foi solicitado.

Quando seu tipo de lista deriva em List<T>vez de Collection<T>, você não pode implementar os métodos virtuais protegidos que Collection<T>implementam. O que isto significa é que o seu tipo derivado não pode responder caso sejam feitas modificações na lista. Isso ocorre porque List<T>assume que você está ciente quando adiciona ou remove itens. Ser capaz de responder a notificações é uma sobrecarga e, portanto List<T>, não a oferece.

Nos casos em que o código externo tem acesso à sua coleção, você pode não estar no controle de quando um item está sendo adicionado ou removido. Portanto, Collection<T>fornece uma maneira de saber quando sua lista foi modificada.

NullReference
fonte
0

Não vejo nenhum problema em retornar algo como

this.InternalData.Filter(crteria).ToList();

Se eu retornei uma cópia desconectada dos dados internos ou o resultado desanexado de uma consulta de dados - posso retornar com segurança List<TItem>sem expor nenhum detalhe da implementação e permitir o uso dos dados retornados da maneira conveniente.

Mas isso depende do tipo de consumidor que eu espero - se é algo como uma grade de dados, prefiro retornar, IEnumerable<TItem> que na maioria dos casos será a lista de itens copiada :)

Konstantin Isaev
fonte
-1

Bem, a classe Collection é realmente apenas uma classe de wrapper em torno de outras coleções para ocultar seus detalhes de implementação e outros recursos. Eu acho que isso tem algo a ver com o padrão de codificação da propriedade oculta nas linguagens orientadas a objetos.

Acho que você não deve se preocupar com isso, mas se realmente deseja agradar a ferramenta de análise de código, faça o seguinte:

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);
Tamas Czinege
fonte
1
Desculpe, foi um erro de digitação. Eu assino a coleção <MyClass>. Estou realmente ansioso para obter minhas mãos em C # 4 e covariância genérica btw!
Tamas Czinege 7/11
Embrulhar em um Collection<T>não protege a si mesmo nem a coleção subjacente, tanto quanto eu entendo.
Nawfal 9/07
Esse trecho de código era "agradar a ferramenta de análise de código". Acho que o @TamasCzinege disse em qualquer lugar que o uso Collection<T>protegerá imediatamente sua coleção subjacente.
chakrit