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.
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.
IEnumerable<string>
, dentro da função eu posso usar a List<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 .
IList
é a promessa. Qual é a promessa menor e melhor aqui? Isso não é lógico.
List<T>
, porque AddRange
não está definido na interface.
IList<T>
faz promessas que não pode cumprir. Por exemplo, existe um Add
método que pode ser lançado se IList<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 temos IReadOnlyCollection<T>
e IReadOnlyList<T>
quais são quase sempre melhores do que IList<T>
. E eles são covariantes. Evite IList<T>
!
IList<T>
. Alguém poderia implementar IReadOnlyList<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>
ou IReadOnlyCollection<T>
deve ser usado para acesso somente leitura.
Algumas pessoas dizem "sempre use em IList<T>
vez de List<T>
".
Eles querem que você altere as assinaturas de método de void Foo(List<T> input)
para void 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>
e List<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:
public Foo(List<int> a)
{
a.Add(someNumber);
}
Um colega útil "refatora" o método a ser aceito IList<int>
.
Seu código agora está quebrado, porque int[]
implementa IList<int>
, mas é de tamanho fixo. O contrato para ICollection<T>
(a base de IList<T>
) requer o código que o utiliza para verificar o IsReadOnly
sinalizador antes de tentar adicionar ou remover itens da coleção. O contrato para List<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.
int[] array = new[] {1, 2, 3};
IList<int> ilist = array;
ilist.Add(4); // throws System.NotSupportedException
ilist.Insert(0, 0); // throws System.NotSupportedException
ilist.Remove(3); // throws System.NotSupportedException
ilist.RemoveAt(0); // throws System.NotSupportedException
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.
if (!ilist.IsReadOnly)
{
ilist.Add(4);
ilist.Insert(0, 0);
ilist.Remove(3);
ilist.RemoveAt(0);
}
else
{
// what were you planning to do if you were given a read only list anyway?
}
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 usa IList<T>
/ ICollection<T>
corretamente . Você não pode garantir que pode substituir um IList<T>
/ ICollection<T>
no código usado List<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ão IEnumerable<T>
? Geralmente, isso é mais adequado se você não precisar adicionar itens. Se você precisar adicionar à coleção, use o tipo de concreto List<T>
,.
Para mim IList<T>
(e ICollection<T>
) é a pior parte do framework .NET. IsReadOnly
viola o princípio da menor surpresa. Uma classe, como Array
, 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 em IList<T>
vez de List<T>
, pergunte a ele como eles adicionariam um elemento a um IList<T>
. Se eles não souberem IsReadOnly
(e a maioria das pessoas não), não use IList<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
IsReadOnly
seja true
para um IList
, você ainda pode modificar seus membros atuais! Talvez essa propriedade deva ser nomeada IsFixedSize
?
Array
como um IList
, então é por isso que eu poderia modificar os membros atuais)
IReadOnlyCollection<T>
e IReadOnlyList<T>
que quase sempre se encaixam melhor IList<T>
do que as semânticas preguiçosas perigosas IEnumerable<T>
. E eles são covariantes. Evite IList<T>
!
List<T>
é uma implementação específica de IList<T>
, que é um contêiner que pode ser endereçado da mesma maneira que uma matriz linear T[]
usando um índice inteiro. Ao especificar IList<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 .NET LinkedList<T>
se não implementar IList<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 a IList<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>
que IList<T>
se estende.
LinkedList<T>
vs taking List<T>
vs IList<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.
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.
public void Foo(IList<Bar> list)
{
// Do Something with the list here.
}
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>.
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)
Reverse
e ToArray
são métodos de extensão para a interface IEnumerable <T>, da qual IList <T> deriva.
List
não em outras implementações de IList
.
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.
E se o .NET 5.0 for substituído System.Collections.Generic.List<T>
por System.Collection.Generics.LinearList<T>
. O .NET sempre possui o nome, List<T>
mas eles garantem que IList<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
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> defines those methods (not including extension methods)
IList<T>
Link MSDN
List<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 MSDN
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.
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
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.
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>
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.