Lista <T> ou IList <T> [fechado]

495

Alguém pode me explicar por que eu gostaria de usar IList sobre List em C #?

Pergunta relacionada: Por que é considerado ruim exporList<T>

Amendoim
fonte
1
procure na internet por "código para uma interface, não implementação" e você pode ler o dia todo ... e encontrar algumas guerras pessoais.
granadaCoder

Respostas:

446

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.

tvanfosson
fonte
19
Não estou entendendo a diferença (sutil), há algum exemplo que você poderia me indicar?
StingyJack
134
Digamos que você tenha usado originalmente a Lista <T> e desejasse mudar para usar um CaseInsensitiveList <T> especializado, os quais implementam IList <T>. Se você usar o tipo concreto, todos os chamadores precisarão ser atualizados. Se exposto como IList <T>, o chamador não precisa ser alterado.
Tvanfosson
46
Ah ESTÁ BEM. Entendi. Escolher o menor denominador comum para um contrato. Obrigado!
StingyJack
16
Este princípio é um exemplo de encapsulamento, um dos três pilares da programação orientada a objetos. A idéia é que você oculte os detalhes da implementação do usuário e forneça a eles uma interface estável. Isso é para reduzir a dependência de detalhes que podem mudar no futuro.
jason
14
Observe também que T []: IList <T>, para que, por razões de desempenho, você sempre possa trocar de retornar uma Lista <T> do seu procedimento para retornar um T [], e se o procedimento for declarado para retornar IList <T> (ou talvez ICollection <T> ou IEnumerable <T>), nada mais precisaria ser alterado.
Yfeldblum
322

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".

Astronautas de software

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.

Arec Barrwin
fonte
65
Eu tenho que discordar da sua resposta sardônica. Não discordo totalmente do sentimento de excesso de arquitetura ser um problema real. No entanto, acho que, especialmente no caso de coleções que interfaces realmente brilham. Digamos que eu tenha uma função que retorne 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".
Joshperry
38
Desenvolvimento de software tem tudo a ver com traduzir intenção. Se você acha que as interfaces são úteis apenas para a construção de arquiteturas grandiosas e de tamanho grande e não têm lugar em pequenas lojas, espero que a pessoa sentada à sua frente na entrevista não seja eu.
Joshperry
48
Alguém tinha que dizer isso Arec ... mesmo que você tenha todos os tipos de padrões acadêmicos perseguindo e-mails de ódio como resultado. +1 para todos os que odeiam quando um aplicativo pequeno é carregado com interfaces e clicar em "encontrar definição" leva a algum outro lugar que não seja a fonte do problema ... Posso emprestar a frase "Astronautas da arquitetura"? Eu posso ver que será útil.
Gats
6
Se possível, darei 1000 pontos para esta resposta. : D
Samuel
8
Eu estava falando sobre o resultado mais amplo da busca de padrões. Interfaces para interfaces comuns boas, camadas extras para perseguir um padrão, ruins.
Gats
186

Interface é uma promessa (ou um contrato).

Como sempre é com as promessas - quanto menor, melhor .

Rinat Abdullin
fonte
56
Nem sempre melhor, apenas mais fácil de manter! :)
Kirk Broadhurst
5
Eu não entendo isso. Então IListé a promessa. Qual é a promessa menor e melhor aqui? Isso não é lógico.
Nawfal
3
Para qualquer outra classe, eu concordo. Mas não para List<T>, porque AddRangenão está definido na interface.
Jesse de Wit
3
Isso não é realmente útil nesse caso específico. A melhor promessa é aquela que é cumprida. IList<T>faz promessas que não pode cumprir. Por exemplo, existe um Addmé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>!
ZunTzu
@ZunTzu Eu diria que alguém passando uma coleção imutável como uma mutável quebrou conscientemente o contrato apresentado - não é um problema com o próprio contrato, por exemplo 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.
Shelby Oldfield
93

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 IsReadOnlysinalizador 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. IsReadOnlyviola 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

perfeccionista
fonte
5
Ótima, ótima resposta. Gostaria de acrescentar que, mesmo que IsReadOnlyseja truepara um IList, você ainda pode modificar seus membros atuais! Talvez essa propriedade deva ser nomeada IsFixedSize?
xofz 25/05
(que foi testado com a passagem de um Arraycomo um IList, então é por isso que eu poderia modificar os membros atuais)
xofz
1
@SamPearson, havia uma propriedade IsFixedSize no IList no .NET 1, não incluída no IList genérico do .NET 2.0 <T>, onde foi consolidada com IsReadOnly, levando a um comportamento surpreendente em torno de Arrays. Há um blog detalhado de derreter o cérebro sobre as diferenças sutis aqui: enterprisecraftsmanship.com/2014/11/22/…
perfeccionista
7
Excelente resposta! Além disso, desde o .NET 4.6, agora temos 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>!
ZunTzu
37

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.

ILoveFortran
fonte
Em relação à sua última declaração sobre o uso de IEnumerable em vez de IList. Isso nem sempre é recomendado; por exemplo, use o
HAdes em
1
Ótima resposta, garantias de desempenho são algo que uma interface não pode oferecer. Em algum código, isso pode ser muito importante e o uso de classes concretas comunica sua intenção, sua necessidade dessa classe específica. Uma interface, por outro lado, diz "Eu só preciso chamar esse conjunto de métodos, nenhum outro contrato está implícito".
Joshperry
1
@joshperry Se o desempenho de uma interface está incomodando, você está na plataforma errada em primeiro lugar. Impo, isso é microoptimização.
Nawfal,
@awawfal Eu não estava comentando sobre os prós / contras de desempenho das interfaces. Eu estava concordando e comentando o fato de que, quando você escolhe expor uma classe concreta como argumento, é capaz de comunicar uma intenção de desempenho. Taking LinkedList<T>vs taking List<T>vs IList<T>all comunicam algo das garantias de desempenho exigidas pelo código que está sendo chamado.
joshperry
28

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.

Patrik Hägne
fonte
1
Maneira muito interessante de pensar sobre isso. E uma boa maneira de pensar ao programar - obrigado.
Amendoim
Bem dito! Como a interface é a abordagem mais flexível, você realmente deve justificar o que a implementação concreta está comprando.
Cory House
9
Ótima maneira de pensar. Ainda assim, posso responder: meu motivo é chamado AddRange ()
PPC
4
minha razão é chamada Add (). às vezes você quer uma lista que você sabe que pode receber elementos adicionais, com certeza? Veja minha resposta.
Perfeccionista #
19

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>

class A : B, IList<T> { ... }
Diadistis
fonte
15

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.

annakata
fonte
3
Se o seu ExtendedList <T> herdar a Lista <T>, você ainda poderá ajustá-lo em um contrato da Lista <T>. Este argumento só funciona se você escrever sua própria implementação de IList <T> de sratch (ou pelo menos sem herdar List <T>)
PPC
14
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>.

smack0007
fonte
13

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)

  • AddRange
  • AsReadOnly
  • BinarySearch
  • Capacidade
  • Converter tudo
  • Existe
  • Encontrar
  • Encontrar tudo
  • FindIndex
  • FindLast
  • FindLastIndex
  • Para cada
  • GetRange
  • InsertRange
  • LastIndexOf
  • Deletar tudo
  • RemoveRange
  • Reverter
  • Ordenar
  • ToArray
  • TrimExcess
  • TrueForAll
JasonS
fonte
1
Não é completamente verdade. Reversee ToArraysão métodos de extensão para a interface IEnumerable <T>, da qual IList <T> deriva.
Tarec
Isso ocorre porque eles só fazem sentido em se Listnão em outras implementações de IList.
HankCa
12

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.

Michael Borgwardt
fonte
7

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

Soundararajan
fonte
7

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

  1. Adicionar
  2. Claro
  3. Contém
  4. Copiar para
  5. GetEnumerator
  6. Índice de
  7. Inserir
  8. Retirar
  9. RemoveAt

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

yantaq
fonte
4

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.

Peter Lindholm
fonte
3

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

StuartLC
fonte
2

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.

vishy
fonte
2

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>

atconway
fonte
0

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.

lazydeveloper
fonte