Qual a diferença entre IEnumerable, IQueryable e List?

Pergunta :


Qual a diferença entre IEnumerable, IQueryable e List no .NET?

Quando é melhor usar uma ou outra?

Por que o ReSharper me sugere modificar o retorno dessa função, por exemplo, de List<T>:

private List<User> GetUsers(){ ... }

para IEnumerable<T>

private IEnumerable<User> GetUsers(){ ... }

?

Autor da pergunta: Andre Figueiredo

Resposta Comunidade:

A sugestão do Resharper ocorre porque costuma ser melhor você lidar com interfaces do que tipos concretos, ou analisando de outra forma usar um tipo mais genérico do que um mais específico.

O fato de gerar um List<T> não significa que você não tem um objeto que não seja um IEnumerable<T>, afinal List<T> é derivado de IEnumerable<T>. Uma lista é uma coleção que permite que seus membros sejam enumerados, ou seja, que você vá analisando elemento por elemento. Um método GetEnumerator() é usado para obter o enumerador (uma espécie de contador para varrer os elementos). E um método MoveNext() (da IEnumerator) faz você avançar para o próximo elemento.

Qualquer objeto IEnumerable<T> permite que cada elemento vá sendo processado individualmente para cada operação necessária ao invés de processar todos elementos da coleção em cada operação separada, o que até impediria certas operações de serem realizadas.

Um IEnumerable<T> costuma ser bom para processar elementos que já estão na memória. Você pode utilizá-lo em uma consulta de banco de dados, mas terá que trazer todos os resultados do banco para a memória para depois processá-lo.

Quando você usa um .ToList(), está convertendo o resultado para uma lista. Você não dá um exemplo de utilização, mas acredito pela pergunta que você recebeu um resultado que um IQueryable<T> que é específico para o LINQ. Um IQueryable<T> também é derivado de um IEnumerable<T> e admite lazy loading permitindo uma melhor otimização de uma consulta. Ou seja, apenas os elementos realmente necessários para uma determinada operação são retornados na consulta para futura análise.

A utilização do IQueryable<T> permite a construção de árvores de expressões de consulta. Costuma ser mais adequado para utilização com banco de dados (LINQ To SQL por exemplo) e outras fontes remotas, principalmente quando precisa de paginação de resultados. Estas expressões podem ser obtidas e executadas com os métodos IQueryProvider.CreateQuery() e IQueryProvider.Execute().

Um resultado IQueryable<T> pode ser convertido para um List<T> mas normalmente é convertido para um IEnumerable<T> para dar mais flexibilidade nas operações seguintes.

Veja em exemplo usando um IEnumerable<T>:

var ent = new EntFuncionarios();
IEnumerable<Funcionario> funcionario = ent.Funcionarios; 
IEnumerable<Funcionario> temp = funcionario.Where(x => x.FuncID == 2).ToList<Funcionario>();

Todos os funcionários virão do banco de dados e depois serão analisados um a um no Where.

E com IQueryable<T>:

var ent = new EntFuncionarios();
IQueryable<Funcionario> funcionario = ent.Funcionarios; 
IEnumerable<Funcionario> temp = funcionario.Where(x => x.FuncID == 2).ToList<Funcionario>();

Uma consulta SQL é criada e somente os dados necessários são trazidos para análise desta consulta.

A maneira como o filtro de dados funciona é a grande diferença. No segundo caso uma consulta é gerada e somente quando se utiliza o .ToList<Funcionario> é que o resultado desta consulta é materializado na memória. Você gera uma consulta e não um resultado final. Este resultado filtrado pode depois ser analisado de forma mais detalhada.

Esta materialização obriga a consulta ser efetivamente executada. Como o IEnumerable<Funcionario> e o IQueryable<Funcionario> são avaliados tardiamente (lazy evaluation ou deferred execution), ou seja, é avaliado sob demanda, de acordo com a necessidade, você só consegue uma lista real quando você gera essa demanda e isto é efeito com a conversão para uma lista com .ToList<Funcionario>.

Coloquei no GitHub para referência futura.

IEnumerable, pertence ao namespace System.Collections, e que expõe um enumerador, que suporta uma iteração sobre uma simples coleção não genérico (MSDN. Interface IEnumerable, Microsoft. 2014. Disponível em: http://msdn.microsoft.com/pt-br/library/System.Collections.IEnumerable(v=vs.110).aspx. Acesso em: 27.mai.2014, tradução) e
IEnumerable<T> que pertence ao namespace System.Collections.Generic que expõe o enumerador, que suporta uma iteração simples sobre uma coleção de um tipo especificado.(MSDN. Interface IEnumerable<T>, Microsoft. 2014. Disponível em: http://msdn.microsoft.com/pt-br/library/9eekhta0(v=vs.110).aspx. Acesso em: 27.mai.2014, tradução). O IEnumerable é a base para todas as coleções que podem ser enumeradas. As interfaces possui um método por padrão a ser implementado o GetEnumerator.

Por essa ilustração percebe a grande importância dessa Interface (IEnumerable e IEnumerable<T>)

Figura 1 – “When to use IEnumerable, ICollection, IList and List”
inserir a descrição da imagem aqui
Fonte: Claudio Bernasconi’s TechBlog

List representa uma lista de objetos fortemente tipados que podem ser acessados pelo índice. Fornece métodos para pesquisar, ordenar e manipular listas. Esse classe possui várias interfaces que a implementam:

  • IList e IList

  • ICollection e ICollection

  • IReadOnlyList

  • IReadOnlyCollection

  • IEnumerable

  • IEnumerable. (MSDN. Classe List<T>. Microsoft. 2014. Disponível em: http://msdn.microsoft.com/pt-br/library/6sh2ey19(v=vs.110).aspx. Acesso em 27.mai.2014, tradução).

Ou seja, dentro de List existem muitos recursos implementados de várias interfaces. Enquanto o IEnumerable tem o comportamento o List implementa tal comportamento, e também o compilador com IEnumerable adia o trabalho até a sua execução final, enquanto o List você força o compilador a gerar os dados imediatamente. No caso do ReSharper, ele faz uma otimização, e lhe dá no caso a melhor estrutura de trabalho daquele metodo, visto que o IEnumerable<User> é bem mais simples do que o List<User>.

IQueryable fornece a funcionalidade para dar valores a consultas em uma fonte de dados específica no qual o tipo de dados não for especificado. O IQueryable interface herda o IEnumerable interface para que se ele representa uma consulta, os resultados dessa consulta podem ser enumerados. (MSDN. Interface IQueryable, Microsoft. 2014. Disponível em: http://msdn.microsoft.com/pt-br/library/system.linq.iqueryable(v=vs.110).aspx. Acesso em: 27.mai.2014, tradução). Como foi dito o IEnumerable é importante para os três itens reportados na questão, é a base dos enumeradores.

Referências:

  • IEnumerable Interface

  • IEnumerable<T> Interface

  • Figura 1

  • Interface IQueryable

IQueryable é uma interface mais específica que possui um provedor de consulta, como o LINQ-To-SQL. É um objeto especial, que ao utilizar um método, ele gera uma query ou uma estrutura de consulta para uma base de dados e retorna estes resultados como uma lista ou um enumerável.

Embora pareça, ele não necessariamente gera apenas comandos SQL: dependendo do provedor de dados, pode-se gerar qualquer consulta através dele, em teoria. Por exemplo, o MongoDB possui seu próprio provedor que gera comandos num formato próprio.

IEnumerable é uma interface que implementa objetos com capacidade de enumeração (IQueryable implementa IEnumerable, inclusive), ou seja, que define um objeto capaz de relacionar outros objetos em sequência.

Não necessariamente IEnumerable é um objeto literal. Em casos em que é usado yield return (ou melhor dizendo, usa-se uma função geradora), o que se obtém é um iterador que implementa a interface. Chamadas a este iterador podem devolver um elemento da sequência ou então a sequência toda, dependendo do método utilizado.

A priori, um objeto que implementa IEnumerable não supõe necessariamente que ele possa ser modificado. Para estes casos, permitindo ordenação ou manipulação dos objetos contidos dentro da sequência, o recomendado pela linguagem é usar um objeto que implemente ICollection (que por sinal também implementa IEnumerable).

List<T> é a implementação de um objeto da interface IEnumerable.

No caso do ReSharper, a sugestão ocorre porque IEnumerable é mais genérico e justamente suporta o retorno como uma função geradora, e não um objeto definido (yield return é a melhor maneira de ilustrar isso).

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *