Quando usar IList e quando usar List


180

Eu sei que IList é a interface e List é o tipo concreto, mas ainda não sei quando usar cada uma. O que estou fazendo agora é que, se eu não precisar dos métodos Sort ou FindAll, uso a interface. Estou certo? Existe uma maneira melhor de decidir quando usar a interface ou o tipo concreto?


1
Se alguém ainda está se perguntando, eu encontrar as melhores respostas aqui: stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

Respostas:


175

Existem duas regras que sigo:

  • Aceite o tipo mais básico que funcionará
  • Retorne o tipo mais rico que seu usuário precisará

Portanto, ao escrever uma função ou método que utiliza uma coleção, escreva-o para não receber uma lista, mas uma IList <T>, uma ICollection <T> ou IEnumerable <T>. As interfaces genéricas ainda funcionarão mesmo para listas heterogêneas porque o System.Object também pode ser um T. Isso poupará sua dor de cabeça se você decidir usar uma pilha ou alguma outra estrutura de dados mais adiante. Se tudo o que você precisa fazer na função é procurar por ela, IEnumerable <T> é realmente tudo o que você deve solicitar.

Por outro lado, ao retornar um objeto de uma função, você deseja fornecer ao usuário o conjunto de operações mais rico possível sem que ele precise se movimentar. Portanto, nesse caso, se for uma Lista <T> internamente, retorne uma cópia como uma Lista <T>.


43
Você não deve tratar os tipos de entrada / saída de maneira diferente. Tipos de entrada e saída deve tanto ser o tipo mais básico (de preferência interface) que irá apoiar as necessidades dos clientes. O encapsulamento depende de informar aos clientes o mínimo possível sobre a implementação da sua classe. Se você retornar uma lista concreta, não poderá mudar para outro tipo melhor sem forçar todos os seus clientes a recompilar / atualizar.
Ash

11
Eu discordo das 2 regras ... Eu usaria o tipo mais primitivo e, principalmente, ao retornar neste caso IList (melhor IEnumarable) e você deve trabalhar com List em sua função interna. Então, quando você precisar "adicionar" ou "classificar", use Coleção, se necessário, e use Lista. Assim, a minha regra dura seria: INÍCIO sempre com IENumarable e se precisar de mais, em seguida, estender ...
Ethem

2
Para sua conveniência, as "duas regras" têm um nome: princípio da robustez (também conhecido como lei de Postel) .
easoncxz

Seja qual for o lado do debate em que alguém deva retornar o tipo mais básico ou o mais rico, algo a considerar é que, ao retornar uma interface muito simplificada, o código de consumo pode, muitas vezes - embora nem sempre - usar uma if...elsecadeia com a ispalavra-chave para descobrir um tipo muito mais rico para ele e acaba lançando nele e usando isso de qualquer maneira. Portanto, você não precisa necessariamente esconder nada usando uma interface básica, em vez de apenas ocultá-la. No entanto, dificultar também pode fazer com que o criador do código que consome pense duas vezes sobre como o está usando.
Panzercrisis 7/09/16

6
Eu discordo muito do ponto 2, especialmente se este estiver em um limite de serviço / API. O retorno de coleções modificáveis ​​pode dar a impressão de que as coleções são "ativas" e chamam métodos como Add()e Remove()podem ter efeitos além da coleção. Retornar uma interface somente leitura, como IEnumerablegeralmente é o caminho a seguir para métodos de recuperação de dados. Seu consumidor pode projetá-lo para um tipo mais rico, conforme necessário.
STW

56

As diretrizes da Microsoft, verificadas pelo FxCop, desencorajam o uso da Lista <T> em APIs públicas - prefira IList <T>.

Aliás, agora quase sempre declaro matrizes unidimensionais como IList <T>, o que significa que posso usar consistentemente a propriedade IList <T> .Count em vez de Array.Length. Por exemplo:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}

3
Eu gosto mais desta explicação / exemplo!
9139 JonH

28

Há uma coisa importante que as pessoas sempre parecem ignorar:

Você pode passar uma matriz simples para algo que aceita um IList<T>parâmetro e, em seguida, você pode chamar IList.Add()e receberá uma exceção de tempo de execução:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

Por exemplo, considere o seguinte código:

private void test(IList<int> list)
{
    list.Add(1);
}

Se você chamar isso da seguinte maneira, receberá uma exceção de tempo de execução:

int[] array = new int[0];
test(array);

Isso acontece porque o uso de matrizes simples com IList<T>viola o princípio de substituição de Liskov.

Por esse motivo, se você estiver ligando, IList<T>.Add()considere solicitar um em List<T>vez de um IList<T>.


Isso é trivialmente verdadeiro para todas as interfaces. Se você quiser prosseguir com o seu argumento, poderá argumentar para nunca usar nenhuma interface, porque alguma implementação dela pode dar certo. Se, por outro lado, considere a sugestão dada pelo OP a preferir List<T>mais IList<T>, você também deve estar ciente das razões pelas quais IList<T>é recomendada. (Por exemplo, blogs.msdn.microsoft.com/kcwalina/2005/09/26/… )
Micha Wiedenmann

3
@MichaWiedenmann Minha resposta aqui é específica para quando você está ligando IList<T>.Add(). Não estou dizendo que você não deve usar IList<T>- estou apenas apontando uma possível armadilha. (I tendem a usar IEnumerable<T>ou IReadOnlyList<T>ou IReadOnlyCollection<T>em detrimento de IList<T>se eu puder.)
Matthew Watson

24

Eu concordo com o conselho de Lee para tomar parâmetros, mas não retornando.

Se você especificar seus métodos para retornar uma interface, significa que você está livre para alterar a implementação exata posteriormente, sem que o método de consumo saiba. Eu pensei que nunca precisaria mudar de uma Lista <T>, mas tive que mudar mais tarde para usar uma biblioteca de listas personalizada para a funcionalidade extra fornecida. Como eu havia retornado apenas um IList <T>, nenhuma das pessoas que usava a biblioteca teve que alterar seu código.

Obviamente, isso só precisa ser aplicado a métodos visíveis externamente (por exemplo, métodos públicos). Eu pessoalmente uso interfaces, mesmo em código interno, mas como você pode alterar todo o código por conta própria, se você fizer alterações recentes, não é estritamente necessário.


22

IEnumerable
Você deve tentar usar o tipo menos específico que se adapte ao seu objetivo.
IEnumerableé menos específico que IList.
Você usa IEnumerablequando deseja percorrer os itens de uma coleção.


IListImplementa IListIEnumerable .
Você deve usar IListquando precisar acessar por índice sua coleção, adicionar e excluir elementos, etc.

Lista de
List implementos IList.


3
Resposta excelente e clara, que marquei como útil. No entanto, eu acrescentaria que, para a maioria dos desenvolvedores, na maioria das vezes, não vale a pena se preocupar com a pequena diferença de tamanho e desempenho do programa: em caso de dúvida, basta usar uma Lista.
Graham Laight

9

É sempre melhor usar o tipo de base mais baixo possível. Isso dá ao implementador da sua interface ou consumidor do seu método a oportunidade de usar o que quiser nos bastidores.

Para coleções, você deve usar o IEnumerable sempre que possível. Isso oferece mais flexibilidade, mas nem sempre é adequado.


1
É sempre melhor aceitar o tipo de base mais baixo possível. Voltar é uma história diferente. Escolha quais opções provavelmente serão úteis. Então você acha que seu cliente pode querer usar o acesso indexado? Evite que eles ToList()retornem IEnumerable<T>uma lista que já era uma lista e, em IList<T>vez disso , retorne uma . Agora, os clientes podem se beneficiar do que você pode fornecer sem esforço.
Timo

5

Se você estiver trabalhando em um único método (ou mesmo em uma única classe ou montagem em alguns casos) e ninguém de fora estiver vendo o que está fazendo, use a totalidade de uma Lista. Mas se você estiver interagindo com código externo, como ao retornar uma lista de um método, você só deseja declarar a interface sem necessariamente se comprometer com uma implementação específica, especialmente se você não tiver controle sobre quem compila contra seu código posteriormente. Se você começou com um tipo concreto e decidiu mudar para outro, mesmo que ele use a mesma interface, você quebrará o código de outra pessoa, a menos que comece com uma interface ou um tipo de base abstrato.


4

Eu não acho que exista regras rígidas para esse tipo de coisa, mas eu geralmente uso a orientação de usar o caminho mais leve possível até que seja absolutamente necessário.

Por exemplo, digamos que você tenha uma Personclasse e uma Groupclasse. Uma Groupinstância tem muitas pessoas, portanto, uma lista aqui faria sentido. Quando eu declarar o objeto de lista Group, usarei um IList<Person>e instanciarei como a List.

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

E, se você nem precisa de tudo, IListtambém pode usá IEnumerable-lo. Com os compiladores e processadores modernos, não acho que haja realmente nenhuma diferença de velocidade, portanto isso é apenas uma questão de estilo.


3
por que não torná-lo apenas uma lista em primeiro lugar? Eu ainda não entendo por que o bônus que você recebe por torná-lo um IList, então no construtor você o transforma em uma lista <> #:
chobo2

Concordo que, se você está criando explicitamente um objeto List <T>, perde a vantagem da interface?
precisa saber é o seguinte

4

Na maioria das vezes, é melhor usar o tipo utilizável mais geral, neste caso, a IList ou, melhor ainda, a interface IEnumerable, para que você possa alternar a implementação convenientemente posteriormente.

No entanto, no .NET 2.0, há algo irritante - o IList não possui um método Sort () . Você pode usar um adaptador fornecido:

ArrayList.Adapter(list).Sort()

2

Você deve usar a interface apenas se precisar, por exemplo, se sua lista for convertida para uma implementação do IList diferente de List. Isso ocorre quando, por exemplo, você usa NHibernate, que converte ILists em um objeto de bolsa NHibernate ao recuperar dados.

Se a Lista for a única implementação que você usará para uma determinada coleção, fique à vontade para declara-la como uma implementação concreta da Lista.


1

Em situações que geralmente encontro, raramente uso o IList diretamente.

Normalmente, eu apenas o uso como argumento para um método

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

Isso permitirá que eu processe genéricos em praticamente qualquer matriz na estrutura .NET, a menos que use IEnumerable e não IList, o que acontece algumas vezes.

Realmente se resume ao tipo de funcionalidade que você precisa. Eu sugiro usar a classe List na maioria dos casos. O IList é o melhor para quando você precisar criar uma matriz personalizada que possa ter algumas regras muito específicas que você gostaria de encapsular em uma coleção, para não se repetir, mas ainda assim desejar que o .NET o reconheça como uma lista.


1

O objeto AList permite que você crie uma lista, adicione coisas a ela, remova-a, atualize-a, indexe-a e etc. A lista é usada sempre que você deseja apenas uma lista genérica na qual você especifica o tipo de objeto e é isso.

IList, por outro lado, é uma interface. Basicamente, se você deseja criar seu próprio tipo de lista, digamos uma classe de lista chamada BookList, use a Interface para fornecer métodos e estrutura básicos para sua nova classe. IList é para quando você deseja criar sua própria subclasse especial que implementa List.

Outra diferença é: IList é uma interface e não pode ser instanciada. Lista é uma classe e pode ser instanciada. Isso significa:

IList<string> MyList = new IList<string>();

List<string> MyList = new List<string>
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.