Alguém pode me explicar por que eu gostaria de usar IList sobre List em C #?
Pergunta relacionada: Por que é considerado ruim exporList<T>
Alguém pode me explicar por que eu gostaria de usar IList sobre List em C #?
Pergunta relacionada: Por que é considerado ruim exporList<T>
Respostas:
Se você está expondo sua classe por meio de uma biblioteca que outras pessoas usarão, geralmente deseja expô-la por meio de interfaces, em vez de implementações concretas. Isso ajudará se você decidir alterar a implementação de sua classe posteriormente para usar uma classe concreta diferente. Nesse caso, os usuários da sua biblioteca não precisarão atualizar o código, pois a interface não muda.
Se você estiver usando apenas internamente, talvez não se importe muito, e o uso
List<T>
pode ser bom.fonte
A resposta menos popular é que os programadores gostam de fingir que seu software será reutilizado em todo o mundo, quando na verdade a maioria dos projetos será mantida por uma pequena quantidade de pessoas e, por mais agradáveis que sejam as mordidas sonoras relacionadas à interface, você está iludindo você mesmo.
Astronautas de arquitetura . As chances de você escrever seu próprio IList que adiciona algo aos que já estão na estrutura .NET são tão remotas que é uma geléia teórica reservada para as "melhores práticas".
Obviamente, se lhe perguntam o que você usa em uma entrevista, você diz IList, sorri e ambos parecem satisfeitos por serem tão espertos. Ou para uma API voltada ao público, IList. Espero que você entenda meu ponto de vista.
fonte
IEnumerable<string>
, dentro da função eu posso usar aList<string>
para um armazenamento interno para gerar minha coleção, mas só quero que os chamadores enumere seu conteúdo, não adicione ou remova. Aceitar uma interface como parâmetro comunica uma mensagem semelhante "Preciso de uma coleção de strings, não se preocupe, não vou alterá-la".Interface é uma promessa (ou um contrato).
Como sempre é com as promessas - quanto menor, melhor .
fonte
IList
é a promessa. Qual é a promessa menor e melhor aqui? Isso não é lógico.List<T>
, porqueAddRange
não está definido na interface.IList<T>
faz promessas que não pode cumprir. Por exemplo, existe umAdd
método que pode ser lançado seIList<T>
for somente leitura.List<T>
por outro lado, é sempre gravável e, portanto, sempre cumpre suas promessas. Além disso, desde o .NET 4.6, agora temosIReadOnlyCollection<T>
eIReadOnlyList<T>
quais são quase sempre melhores do queIList<T>
. E eles são covariantes. EviteIList<T>
!IList<T>
. Alguém poderia implementarIReadOnlyList<T>
e fazer com que todos os métodos também lançassem uma exceção. É o oposto da digitação de pato - você pode chamar de pato, mas não pode grasnar como um. Isso faz parte do contrato, mesmo que o compilador não possa cumpri-lo. Eu concordo 100% , porém,IReadOnlyList<T>
ouIReadOnlyCollection<T>
deve ser usado para acesso somente leitura.Algumas pessoas dizem "sempre use em
IList<T>
vez deList<T>
".Eles querem que você altere as assinaturas de método de
void Foo(List<T> input)
paravoid Foo(IList<T> input)
.Essas pessoas estão erradas.
É mais matizado do que isso. Se você estiver retornando um
IList<T>
como parte da interface pública para sua biblioteca, deixa opções interessantes para talvez fazer uma lista personalizada no futuro. Você pode nem precisar dessa opção, mas é uma discussão. Eu acho que é o argumento inteiro para retornar a interface em vez do tipo concreto. Vale ressaltar, mas neste caso, tem uma falha séria.Como contra-argumento menor, você pode achar que todo chamador precisa de
List<T>
qualquer maneira e o código de chamada está repleto de.ToList()
Mas muito mais importante, se você estiver aceitando um IList como parâmetro, é melhor ter cuidado, porque
IList<T>
eList<T>
não se comporta da mesma maneira. Apesar da semelhança no nome e apesar de compartilhar uma interface, eles não expõem o mesmo contrato .Suponha que você tenha este método:
Um colega útil "refatora" o método a ser aceito
IList<int>
.Seu código agora está quebrado, porque
int[]
implementaIList<int>
, mas é de tamanho fixo. O contrato paraICollection<T>
(a base deIList<T>
) requer o código que o utiliza para verificar oIsReadOnly
sinalizador antes de tentar adicionar ou remover itens da coleção. O contrato paraList<T>
não.O Princípio de Substituição de Liskov (simplificado) afirma que um tipo derivado deve poder ser usado no lugar de um tipo base, sem pré-condições ou pós-condições adicionais.
Parece que isso quebra o princípio da substituição de Liskov.
Mas isso não acontece. A resposta para isso é que o exemplo usou IList <T> / ICollection <T> errado. Se você usa um ICollection <T>, precisa verificar o sinalizador IsReadOnly.
Se alguém passar uma matriz ou uma lista para você, seu código funcionará bem se você verificar a sinalização toda vez e tiver um fallback ... Mas realmente; quem faz isso? Você não sabe com antecedência se seu método precisa de uma lista que pode receber membros adicionais; você não especifica isso na assinatura do método? O que exatamente você faria se fosse aprovada em uma lista somente leitura
int[]
?Você pode substituir um
List<T>
código que usaIList<T>
/ICollection<T>
corretamente . Você não pode garantir que pode substituir umIList<T>
/ICollection<T>
no código usadoList<T>
.Há um apelo ao Princípio de Responsabilidade Única / Princípio de Segregação de Interface em muitos argumentos para usar abstrações em vez de tipos concretos - depende da interface mais estreita possível. Na maioria dos casos, se você estiver usando um
List<T>
e acha que poderia usar uma interface mais estreita - por que nãoIEnumerable<T>
? Geralmente, isso é mais adequado se você não precisar adicionar itens. Se você precisar adicionar à coleção, use o tipo de concretoList<T>
,.Para mim
IList<T>
(eICollection<T>
) é a pior parte do framework .NET.IsReadOnly
viola o princípio da menor surpresa. Uma classe, comoArray
, que nunca permite adicionar, inserir ou remover itens, não deve implementar uma interface com os métodos Adicionar, Inserir e Remover. (consulte também /software/306105/implementing-an-interface-when-you-dont-need-one-of-the-properties )É
IList<T>
uma boa opção para sua organização? Se um colega solicitar que você altere uma assinatura de método a ser usada emIList<T>
vez deList<T>
, pergunte a ele como eles adicionariam um elemento a umIList<T>
. Se eles não souberemIsReadOnly
(e a maioria das pessoas não), não useIList<T>
. Sempre.Observe que o sinalizador IsReadOnly vem de ICollection <T> e indica se itens podem ser adicionados ou removidos da coleção; mas apenas para realmente confundir as coisas, isso não indica se elas podem ser substituídas, o que no caso de Arrays (que retornam IsReadOnlys == true) pode ser.
Para saber mais sobre IsReadOnly, consulte a definição de ICollection <T> .IsReadOnly do msdn
fonte
IsReadOnly
sejatrue
para umIList
, você ainda pode modificar seus membros atuais! Talvez essa propriedade deva ser nomeadaIsFixedSize
?Array
como umIList
, então é por isso que eu poderia modificar os membros atuais)IReadOnlyCollection<T>
eIReadOnlyList<T>
que quase sempre se encaixam melhorIList<T>
do que as semânticas preguiçosas perigosasIEnumerable<T>
. E eles são covariantes. EviteIList<T>
!List<T>
é uma implementação específica deIList<T>
, que é um contêiner que pode ser endereçado da mesma maneira que uma matriz linearT[]
usando um índice inteiro. Ao especificarIList<T>
como o tipo do argumento do método, você especifica apenas que precisa de certos recursos do contêiner.Por exemplo, a especificação da interface não impõe uma estrutura de dados específica a ser usada. A implementação de
List<T>
acontece com o mesmo desempenho para acessar, excluir e adicionar elementos como uma matriz linear. No entanto, você pode imaginar uma implementação que é apoiada por uma lista vinculada, para a qual adicionar elementos ao final é mais barato (tempo constante), mas o acesso aleatório é muito mais caro. (Note-se que o .NETLinkedList<T>
se não implementarIList<T>
.)Este exemplo também informa que pode haver situações em que você precisa especificar a implementação, e não a interface, na lista de argumentos: Neste exemplo, sempre que você precisar de uma determinada característica de desempenho de acesso. Isso geralmente é garantido para uma implementação específica de um contêiner (
List<T>
documentação: "Implementa aIList<T>
interface genérica usando uma matriz cujo tamanho é aumentado dinamicamente conforme necessário.").Além disso, convém expor o mínimo de funcionalidade necessária. Por exemplo. se você não precisar alterar o conteúdo da lista, provavelmente considere usar o
IEnumerable<T>
queIList<T>
se estende.fonte
LinkedList<T>
vs takingList<T>
vsIList<T>
all comunicam algo das garantias de desempenho exigidas pelo código que está sendo chamado.Eu mudaria um pouco a questão, em vez de justificar por que você deveria usar a interface sobre a implementação concreta, tentaria justificar por que você usaria a implementação concreta em vez da interface. Se você não pode justificar, use a interface.
fonte
IList <T> é uma interface para que você possa herdar outra classe e ainda implementar IList <T> enquanto a herança de Lista <T> impede que você faça isso.
Por exemplo, se houver uma classe A e sua classe B a herdar, você não poderá usar a Lista <T>
fonte
Um princípio de TDD e OOP geralmente é a programação para uma interface, não para uma implementação.
Nesse caso específico, uma vez que você está falando essencialmente sobre uma construção de linguagem, não uma customizada, geralmente não importa, mas digamos, por exemplo, que você encontrou a Lista não suportava algo que precisava. Se você tivesse usado o IList no restante do aplicativo, poderia estender a Lista com sua própria classe personalizada e ainda assim conseguir distribuir isso sem refatorar.
O custo para fazer isso é mínimo, por que não salvar a dor de cabeça mais tarde? É disso que se trata o princípio da interface.
fonte
Nesse caso, você pode passar em qualquer classe que implemente a interface IList <Bar>. Se você usasse List <Bar>, apenas uma instância de List <Bar> poderia ser transmitida.
O caminho IList <Bar> é mais frouxamente acoplado do que o caminho List <Bar>.
fonte
Supondo que nenhuma dessas perguntas (ou respostas) da Lista versus IList mencione a diferença de assinatura. (É por isso que procurei essa pergunta no SO!)
Então, aqui estão os métodos contidos na Lista que não são encontrados no IList, pelo menos a partir do .NET 4.5 (por volta de 2015)
fonte
Reverse
eToArray
são métodos de extensão para a interface IEnumerable <T>, da qual IList <T> deriva.List
não em outras implementações deIList
.O caso mais importante para o uso de interfaces em implementações está nos parâmetros da sua API. Se sua API usa um parâmetro List, qualquer pessoa que a use precisará usar List. Se o tipo de parâmetro for IList, o chamador terá muito mais liberdade e poderá usar as classes que você nunca ouviu falar, que podem até não existir quando o código foi gravado.
fonte
E se o .NET 5.0 for substituído
System.Collections.Generic.List<T>
porSystem.Collection.Generics.LinearList<T>
. O .NET sempre possui o nome,List<T>
mas eles garantem queIList<T>
é um contrato. Portanto, IMHO nós (pelo menos eu) não devemos usar o nome de alguém (embora seja .NET neste caso) e ter problemas mais tarde.No caso de uso
IList<T>
, o chamador sempre tem as coisas recomendadas para o trabalho e o implementador é livre para alterar a coleção subjacente para qualquer implementação concreta alternativa deIList
fonte
Todos os conceitos são basicamente declarados na maioria das respostas acima, sobre o porquê de usar a interface em implementações concretas.
IList<T>
Link MSDNList<T>
implementa esses nove métodos (não incluindo métodos de extensão), além disso, possui cerca de 41 métodos públicos, que pesam na sua consideração sobre qual deles usar em seu aplicativo.List<T>
Link MSDNfonte
Você faria porque definir uma IList ou uma ICollection se abriria para outras implementações de suas interfaces.
Você pode querer ter um IOrderRepository que defina uma coleção de pedidos em um IList ou ICollection. Você pode ter diferentes tipos de implementações para fornecer uma lista de pedidos, desde que eles estejam em conformidade com as "regras" definidas por sua IList ou ICollection.
fonte
O IList <> é quase sempre preferível, de acordo com os conselhos do outro pôster, no entanto, observe que há um erro no .NET 3.5 sp 1 ao executar um IList <> através de mais de um ciclo de serialização / desserialização com o WCF DataContractSerializer.
Agora existe um SP para corrigir esse erro: KB 971030
fonte
A interface garante que você obtenha pelo menos os métodos esperados ; estar ciente da definição da interface ou seja. todos os métodos abstratos que existem para serem implementados por qualquer classe que herda a interface. portanto, se alguém criar uma classe enorme com vários métodos além dos que ele herdou da interface para obter alguma funcionalidade adicional, e esses não forem úteis para você, é melhor usar uma referência a uma subclasse (nesse caso, o interface) e atribua o objeto de classe concreto a ele.
Uma vantagem adicional é que seu código está protegido contra qualquer alteração na classe concreta, pois você está assinando apenas alguns dos métodos da classe concreta e esses são os que existirão enquanto a classe concreta herdar da interface em que você está. usando. portanto, é uma segurança para você e liberdade para o codificador que está escrevendo uma implementação concreta para alterar ou adicionar mais funcionalidade à sua classe concreta.
fonte
Você pode observar esse argumento de vários ângulos, incluindo o de uma abordagem puramente OO que diz programar contra uma interface e não uma implementação. Com esse pensamento, o uso do IList segue o mesmo princípio de repassar e usar interfaces que você define do zero. Eu também acredito nos fatores de escalabilidade e flexibilidade fornecidos por uma interface em geral. Se uma classe que implementa IList <T> precisar ser estendida ou alterada, o código de consumo não precisará ser alterado; ele sabe a que o contrato da IList Interface cumpre. No entanto, o uso de uma implementação concreta e a List <T> em uma classe que é alterada podem fazer com que o código de chamada também precise ser alterado. Isso ocorre porque uma classe aderente ao IList <T> garante um determinado comportamento que não é garantido por um tipo concreto usando a Lista <T>.
Também ter o poder de fazer algo como modificar a implementação padrão de List <T> em uma classe Implementing IList <T>, por exemplo, o .Add, .Remove ou qualquer outro método IList, oferece ao desenvolvedor muita flexibilidade e poder, caso contrário predefinidos por Lista <T>
fonte
Normalmente, uma boa abordagem é usar o IList em sua API voltada ao público (quando apropriado, e semântica de lista são necessárias) e, em seguida, Listar internamente para implementar a API. Isso permite que você mude para uma implementação diferente do IList sem quebrar o código que usa sua classe.
O nome da classe List pode ser alterado na próxima estrutura .net, mas a interface nunca será alterada, pois a interface é contrato.
Observe que, se sua API for usada apenas em loops foreach etc., convém expor IEnumerable.
fonte