Devo sempre retornar IEnumerable <T> em vez de IList <T>?

97

Quando estou escrevendo meu DAL ou outro código que retorna um conjunto de itens, devo sempre fazer minha declaração de retorno:

public IEnumerable<FooBar> GetRecentItems()

ou

public IList<FooBar> GetRecentItems()

Atualmente, tenho tentado usar o IEnumerable em meu código o máximo possível, mas não tenho certeza se essa é a prática recomendada. Parecia correto porque eu estava retornando o tipo de dados mais genérico, embora ainda descritivo do que ele faz, mas talvez isso não seja correto fazer.

KingNestor
fonte
1
É List <T> ou IList <T> que você está perguntando? O título e a pergunta dizem coisas diferentes ...
Fredrik Mörk
Sempre que possível, a interface com o usuário IEnumerable ou Ilist é do tipo concerete.
Usman Masood
2
Eu retorno coleções como List <T>. Não vejo necessidade de retornar IEnumberable <T> já que você pode extrair isso da Lista <T>
Chuck Conway
possível duplicata de ienumerablet-as-return-type
nawfal

Respostas:

44

Realmente depende de por que você está usando essa interface específica.

Por exemplo, IList<T>tem vários métodos que não estão presentes em IEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

e propriedades:

  • T this[int index] { get; set; }

Se você precisar desses métodos de alguma forma, volte por todos os meios IList<T>.

Além disso, se o método que consome seu IEnumerable<T>resultado estiver esperando um IList<T>, ele evitará que o CLR considere quaisquer conversões necessárias, otimizando assim o código compilado.

Jon Limjap
fonte
2
@Jon FDG recomenda usar Collection <T> ou ReadOnlyCollection <T> como um valor de retorno para tipos de coleção, veja minha resposta.
Sam Saffron
Você não tem certeza do que quer dizer em sua última frase quando diz "isso salvará o CLR". O que o salvará, usando IEnumerable vs. IList? Você pode deixar isso mais claro?
PositiveGuy
1
@CoffeeAddict Três anos após essa resposta, acho que você está certo - a última parte é vaga. Se um método que espera um IList <T> como parâmetro obtém um IEnumerable <T>, o IEnumerable deve ser empacotado manualmente em um novo List <T> ou outro implementador IList <T>, e esse trabalho não será feito por o CLR para você. O oposto - um método que espera um IEnumerable <T> obtendo um IList <T>, pode ter que fazer algum unboxing, mas em retrospectiva pode não precisar porque IList <T> implementa IEnumerable <T>.
Jon Limjap
68

As diretrizes de design da estrutura recomendam o uso da classe Collection quando você precisa retornar uma coleção que pode ser modificada pelo chamador ou ReadOnlyCollection para coleções somente leitura.

A razão pela qual é preferível a um simples IListé que IListnão informa ao chamador se é somente leitura ou não.

Se você retornar um em IEnumerable<T>vez disso, certas operações podem ser um pouco mais complicadas para o chamador realizar. Além disso, você não dará mais ao chamador a flexibilidade de modificar a coleção, algo que você pode ou não querer.

Lembre-se de que o LINQ contém alguns truques na manga e otimizará certas chamadas com base no tipo em que são realizadas. Portanto, por exemplo, se você executar Countae a coleção subjacente for uma Lista, ela NÃO percorrerá todos os elementos.

Pessoalmente, para um ORM, provavelmente ficaria com Collection<T>meu valor de retorno.

Sam Saffron
fonte
12
As Diretrizes para Cobranças contêm uma lista mais detalhada de FAZER e FAZER.
Chaquotay
26

Em geral, você deve exigir o mais genérico e retornar o mais específico que puder. Portanto, se você tiver um método que usa um parâmetro e realmente só precisa do que está disponível em IEnumerable, esse deve ser o seu tipo de parâmetro. Se o seu método puder retornar um IList ou um IEnumerable, prefira retornar IList. Isso garante que seja utilizável por uma ampla gama de consumidores.

Seja frouxo no que você precisa e explícito no que você fornece.

Mel
fonte
1
Eu cheguei à conclusão oposta: que devemos aceitar tipos específicos e retornar tipos gerais. Você pode estar certo, entretanto, que métodos mais amplamente aplicáveis ​​são melhores do que métodos mais restritos. Terei que pensar mais sobre isso.
Dave Cousineau
3
A justificativa para aceitar tipos gerais como entrada é que isso permite que você trabalhe com a mais ampla gama de entrada possível para obter o máximo possível de reutilização de um componente. Por outro lado, como você já sabe exatamente que tipo de objeto tem em mãos, não há muito sentido em mascará-lo.
Mel
1
Acho que concordo em ter parâmetros mais gerais, mas qual é o seu raciocínio para retornar algo menos geral?
Dave Cousineau
6
Ok, deixe-me tentar de outra maneira. Por que você jogaria informações fora? Se você só se preocupa com o resultado sendo IEnumerable <T>, prejudica você saber que é um IList <T>? Não, não importa. Pode ser uma informação supérflua em alguns casos, mas não lhe faz mal. Agora, para o benefício. Se você retornar um List ou IList, posso dizer imediatamente que a coleção já foi recuperada, algo que não posso saber com um IEnumerable. Isso pode ou não ser uma informação útil, mas, mais uma vez, por que você jogaria informações fora? Se você souber de informações extras sobre algo, passe adiante.
Mel de
Em retrospecto, estou surpreso que ninguém nunca tenha me chamado sobre isso. Um IEnumerable já teria sido recuperado. Um IQueryable pode ser retornado em um estado não enumerado, no entanto. Portanto, talvez um exemplo melhor seria retornar IQueryable vs IEnumerable. Retornar o IQueryable mais específico permitiria uma composição adicional, ao passo que retornar IEnumerable forçaria a enumeração imediata e diminuiria a capacidade de composição do método.
Mel de
23

Depende...

Retornar o tipo menos derivado ( IEnumerable) deixará a você mais margem de manobra para alterar a implementação subjacente no futuro.

Retornar um type mais derivado ( IList) fornece aos usuários de sua API mais operações no resultado.

Eu sempre sugeriria retornar o tipo menos derivado que tem todas as operações de que seus usuários vão precisar ... então, basicamente, você primeiro tem que determinar quais operações no resultado fazem sentido no contexto da API que você está definindo.

Jerryjvl
fonte
Uma boa resposta genérica que se aplica a outros métodos também.
user420667
1
Eu sei que esta resposta é muito antiga ... mas parece contradizer a documentação: "Se nem a interface IDictionary <TKey, TValue> nem a interface IList <T> atenderem aos requisitos da coleção necessária, derivar a nova classe de coleção de a interface ICollection <T> em vez para mais flexibilidade "Isso parece implicar que o tipo mais derivado deve ser preferido. ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK
11

Uma coisa a considerar é que, se você estiver usando uma instrução LINQ de execução adiada para gerar seu IEnumerable<T>, chamar .ToList()antes de retornar do seu método significa que seus itens podem ser iterados duas vezes - uma para criar a Lista e uma vez quando o chamador faz um loop através , filtra ou transforma seu valor de retorno. Quando prático, gosto de evitar converter os resultados do LINQ-to-Objects em uma lista ou dicionário concreto até que seja necessário. Se meu chamador precisa de uma lista, é apenas uma chamada de método fácil - não preciso tomar essa decisão por eles, e isso torna meu código um pouco mais eficiente nos casos em que o chamador está apenas fazendo um foreach.

Joel Mueller
fonte
@Joel Mueller, geralmente chamaria ToList () para eles de qualquer maneira. Eu geralmente não gosto de expor IQueryable para o resto dos meus projetos.
KingNestor
4
Eu estava me referindo mais a LINQ-to-Objects, onde IQueryable geralmente não entra em cena. Quando um banco de dados está envolvido, ToList () torna-se mais necessário, pois caso contrário você corre o risco de fechar sua conexão antes de iterar, o que não funciona muito bem. No entanto, quando isso não é um problema, é muito fácil expor IQueryable como um IEnumerable sem forçar uma iteração extra quando você deseja ocultar o IQueryable.
Joel Mueller
9

List<T>oferece ao código de chamada muitos outros recursos, como modificar o objeto retornado e acessar por índice. Portanto, a questão se resume a: no caso de uso específico do seu aplicativo, você DESEJA oferecer suporte a tais usos (presumivelmente retornando uma coleção recém-construída!), Para a conveniência do chamador - ou você quer velocidade para o caso simples quando todos os O chamador precisa é percorrer a coleção e você pode retornar com segurança uma referência a uma coleção subjacente real, sem temer que isso vá alterá-la erroneamente, etc?

Só você pode responder a esta pergunta, e apenas entendendo bem o que seus chamadores vão querer fazer com o valor de retorno e quão importante é o desempenho aqui (quão grandes são as coleções que você estaria copiando, qual a probabilidade de isso ser um gargalo, etc).

Alex Martelli
fonte
"retornar com segurança uma referência a uma coleção subjacente real, sem temer que isso vá alterá-la erroneamente" - Mesmo se você retornar IEnumerable <T>, eles não poderiam simplesmente lançá-la de volta em List <T> e alterá-la?
Kobi
Nem todo IEnumarable <T> também é um List <T>. Se o objeto retornado não for de um tipo que herda de List <T> ou implementa IList <T>, isso resultaria em uma InvalidCastException.
lowglider
2
List <T> tem o problema de bloquear você em uma implementação específica Collection <T> ou ReadOnlyCollection <T> são preferidos
Sam Saffron
4

Acho que você pode usar qualquer um, mas cada um tem uma utilidade. Basicamente Listé, IEnumerablemas você tem a funcionalidade de contagem, adicionar elemento, remover elemento

IEnumerable não é eficiente para contar elementos

Se a coleção se destina a ser somente leitura ou a modificação da coleção é controlada pelo Parent, retornar um IListjust Countfor não é uma boa ideia.

No Linq, há um Count()método de extensão no IEnumerable<T>qual dentro do CLR atalho para .Countse o tipo subjacente for de IList, portanto, a diferença de desempenho é insignificante.

Geralmente eu sinto (opinião) que é uma prática melhor retornar IEnumerable onde possível, se você precisar fazer adições, então adicione esses métodos à classe pai, caso contrário, o consumidor está gerenciando a coleção dentro de Model que viola os princípios, por exemplo, manufacturer.Models.Add(model)viola a lei de demeter. É claro que essas são apenas diretrizes e não regras rígidas e rápidas, mas até que você tenha total compreensão da aplicabilidade, seguir cegamente é melhor do que não seguir de forma alguma.

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(Nota: Se estiver usando nNHibernate, você pode precisar mapear para IList privado usando acessores diferentes.)

Usuário SO
fonte
2

Não é tão simples quando você está falando sobre valores de retorno em vez de parâmetros de entrada. Quando é um parâmetro de entrada, você sabe exatamente o que precisa fazer. Portanto, se você precisar iterar na coleção, pegue um IEnumberable, enquanto se precisar adicionar ou remover, você pega um IList.

No caso de um valor de retorno, é mais difícil. O que seu chamador espera? Se você retornar um IEnumerable, ele não saberá a priori que pode fazer um IList disso. Mas, se você retornar um IList, ele saberá que pode iterar sobre ele. Portanto, você deve levar em consideração o que seu chamador fará com os dados. A funcionalidade que seu chamador precisa / espera é o que deve reger ao tomar a decisão sobre o que retornar.

JP Alioto
fonte
0

como todos disseram, depende, se você não quiser Adicionar / Remover funcionalidade na camada de chamada, então votarei no IEnumerable, pois ele fornece apenas iteração e funcionalidade básica que em prespectiva de design eu gosto. Retornando IList meus votos são sempre novamente, mas é principalmente o que você gosta e o que não é. em termos de desempenho, acho que são mais do mesmo.

Usman Masood
fonte
0

Se você não contar em seu código externo é sempre melhor retornar IEnumerable, porque posteriormente você pode alterar sua implementação (sem impacto no código externo), por exemplo, para render a lógica do iterador e conservar recursos de memória (recurso de linguagem muito bom por sinal )

No entanto, se você precisar da contagem de itens, não se esqueça de que há outra camada entre IEnumerable e IList - ICollection .

árbitro
fonte
0

Posso estar um pouco errado aqui, visto que ninguém mais sugeriu até agora, mas por que você não retorna um (I)Collection<T>?

Pelo que me lembro, Collection<T>era o tipo de retorno preferencial List<T>porque abstrai a implementação. Todos eles implementam IEnumerable, mas isso me parece um nível um pouco baixo demais para o trabalho.

Ana tiberiu
fonte
0

Acho que você pode usar qualquer um, mas cada um tem uma utilidade. Basicamente Listé, IEnumerablemas você tem a funcionalidade de contagem, Adicionar elemento, remover elemento

IEnumerable não é eficiente para contar elementos ou obter um elemento específico na coleção.

List é uma coleção ideal para encontrar elementos específicos, fácil de adicionar ou remover.

Geralmente tento usar sempre Listque possível, pois isso me dá mais flexibilidade.

Use em List<FooBar> getRecentItems() vez de IList<FooBar> GetRecentItems()

Stuart
fonte
0

Acho que a regra geral é usar a classe mais específica para retornar, para evitar fazer trabalhos desnecessários e dar mais opções ao chamador.

Dito isso, acho que é mais importante considerar o código à sua frente, que você está escrevendo, do que o código que o próximo cara escreverá (dentro do razoável). Isso ocorre porque você pode fazer suposições sobre o código que já existe.

Lembre-se de que mover UP para uma coleção de IEnumerable em uma interface funcionará, mover para baixo para IEnumerable de uma coleção quebrará o código existente.

Se todas essas opiniões parecem conflitantes, é porque a decisão é subjetiva.

Sprague
fonte
0

TL; DR; - resumo

  • Se você desenvolve software interno, use o tipo específico (Like List) para os valores de retorno e o tipo mais genérico para os parâmetros de entrada, mesmo no caso de coleções.
  • Se um método fizer parte da API pública de uma biblioteca redistribuível, use interfaces em vez de tipos de coleção concretos para introduzir valores de retorno e parâmetros de entrada.
  • Se um método retornar uma coleção somente leitura, mostre isso usando IReadOnlyListou IReadOnlyCollectioncomo o tipo de valor de retorno.

Mais

Ali Bayat
fonte