IEnumerable<T> vs IQueryable<T>: o que escolher em C#? Análise completa

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

IEnumerable<T> vs IQueryable<T>: o duelo que decidirá o destino da sua aplicação

\n\n

Você já se perguntou por que sua consulta ao banco de dados está lenta, mesmo com poucos dados? Ou por que, após adicionar uma linha de código, a aplicação vira uma tartaruga? Provavelmente, você se deparou com o clássico dilema: IEnumerable<T> ou IQueryable<T>? Não são apenas duas interfaces do mundo C# e LINQ. São dois mundos diferentes, duas abordagens para trabalhar com dados. Um é para coleções locais, o outro para fontes remotas, como bancos de dados. Neste artigo, não vamos apenas listar as diferenças, mas analisá-las com exemplos reais, para que você nunca mais caia na armadilha do desempenho.

\n\n

Quem é quem: conceitos básicos

\n\n

Vamos começar pelo básico. IEnumerable<T> é uma interface que representa uma sequência de elementos na memória. É ideal para trabalhar com coleções como List<T>, Array ou Dictionary. Ao usar IEnumerable, todas as operações (filtragem, ordenação) são executadas no lado do cliente, ou seja, na sua aplicação.

\n\n

IQueryable<T> é uma interface mais avançada, projetada para trabalhar com fontes de dados externas (bancos de dados, serviços web). Ela não armazena dados, mas constrói uma árvore de expressão (Expression Tree). Isso permite que o provedor LINQ (por exemplo, Entity Framework) transforme seu código C# em uma consulta SQL eficiente e a execute no lado do servidor.

\n\n

A frase-chave para lembrar: IEnumerable opera na memória, IQueryable opera no lado da fonte de dados.

\n\n

Diferença principal: onde e quando a lógica é executada

\n\n

Imagine que você precisa filtrar uma lista de 10.000 produtos que custam mais de R$ 1.000 e ordená-los por preço.

\n\n

Exemplo 1: IEnumerable (filtragem na memória)

\n
// Suponha que products seja List<Product> (IEnumerable)\nvar expensiveProducts = products\n    .Where(p => p.Price > 1000)\n    .OrderBy(p => p.Price)\n    .ToList();\n
\n

O que acontece aqui? Primeiro, TODOS os 10.000 produtos são carregados na memória. Em seguida, o C# aplica o filtro Where e a ordenação OrderBy na sua aplicação. Se você tem 10.000 registros, não é crítico. Mas se forem 10 milhões — a aplicação pode travar ou congelar.

\n\n

Exemplo 2: IQueryable (filtragem no servidor do banco de dados)

\n
// Suponha que dbContext.Products seja DbSet<Product> (IQueryable)\nvar expensiveProducts = dbContext.Products\n    .Where(p => p.Price > 1000)\n    .OrderBy(p => p.Price)\n    .ToList();\n
\n

Aqui está a mágica: IQueryable não carrega os dados imediatamente. Em vez disso, ele constrói uma consulta SQL: SELECT * FROM Products WHERE Price > 1000 ORDER BY Price. Essa consulta é enviada ao banco de dados, que retorna apenas os dados filtrados e ordenados. Na memória, você recebe apenas o que precisa. A economia de recursos é colossal.

\n\n

Exemplo prático: a armadilha do ToList()

\n\n

Um dos erros mais comuns de desenvolvedores iniciantes é chamar ToList() ou ToArray() prematuramente. Isso transforma IQueryable em IEnumerable.

\n\n

Código ruim (mata o desempenho):

\n
var allProducts = dbContext.Products.ToList(); // Carrega TODOS os dados na memória!\nvar filteredProducts = allProducts.Where(p => p.Price > 1000).ToList();\n
\n

Neste exemplo, primeiro carregamos toda a tabela Products na memória e depois a filtramos. É o equivalente a descarregar todo o estoque no estacionamento para depois procurar a caixa certa na pilha.

\n\n

Código bom (ideal):

Blogs

Book Recommendations