Expression<Func<T>> vs Func<T>: o segredo da otimização em C#

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

Expression\n\n

Imagine: você está escrevendo código C#. Precisa passar uma condição de filtro para um método. O que escolher: Func<User, bool> ou Expression<Func<User, bool>>? Aparentemente a diferença é mínima — ambos recebem um usuário e retornam bool. Mas nos bastidores, esconde-se um universo inteiro de diferenças.

\n\n

No Stack Overflow, essa pergunta acumulou milhares de visualizações — e não é à toa. Hoje vamos entender por que Expression<Func<T>> pode ser mais poderoso, mais rápido e mais seguro que o Func<T> comum. E o mais importante — quando usar cada um deles para não dar um tiro no pé.

\n\n

Breve introdução: o que são Func e Expression?

\n\n

Func<T> é simplesmente um delegate. Uma função pronta para execução, código de máquina compilado. Você passa dados para ela — ela os processa. Rápido, eficiente, mas… opaco.

\n\n
Func<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // executa instantaneamente
\n\n

Expression<Func<T>> é uma árvore de expressão. Não é código, mas sua descrição. Você não pode simplesmente chamá-lo — precisa compilá-lo em um delegate (através de .Compile()) ou analisar sua estrutura.

\n\n
Expression<Func<User, bool>> expr = user => user.Age >= 18;\n// Não pode simplesmente chamar: expr(someUser) — erro de compilação\nvar compiled = expr.Compile();\nbool result = compiled(someUser); // agora pode
\n\n

Parece um trabalho extra desnecessário? Mas é exatamente essa "descritividade" que abre a porta para a magia.

\n\n

Cenário 1: LINQ to Objects (trabalhando em memória)

\n\n

Quando você trabalha com coleções em memória (por exemplo, List<T>), o LINQ to Objects usa Func<T>. Árvores de expressão não são necessárias aqui — tudo é executado no mesmo processo.

\n\n
var users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>
\n\n

O desempenho é excelente: sem custos adicionais de análise de árvore. Se você trabalha apenas com dados locais — use Func sem medo. É mais rápido e mais simples.

\n\n

Cenário 2: LINQ to SQL / Entity Framework (trabalhando com BD)

\n\n

E aqui começa a magia. Quando você escreve:

\n\n
var adults = dbContext.Users.Where(u => u.Age >= 18).ToList();
\n\n

O Entity Framework não executa seu código C#. Em vez disso, ele analisa a árvore de expressão passada e a converte em uma consulta SQL:

\n\n
SELECT * FROM Users WHERE Age >= 18
\n\n

Se você tivesse passado um Func, o EF não conseguiria "olhar para dentro" — ele simplesmente carregaria TODOS os usuários da tabela e executaria o filtro em memória. Imagine: uma tabela com 10 milhões de registros — todos eles viajando pela rede até sua aplicação para serem filtrados no cliente.

\n\n

Expression<Func<T>> permite que o EF (ou qualquer outro provedor IQueryable) leia sua condição, traduza para a linguagem do banco de dados (SQL, MongoDB query, OData, etc.) e execute o filtro no servidor.

\n\n

Desempenho: benchmarks e números reais

\n\n

Vamos olhar para os números. Vamos pegar um exemplo simples: filtrar 100.000 usuários por idade.

\n\n
// Opção 1: Func — carregamos todos, filtramos em memória\nvar allUsers = dbContext.Users.ToList(); // SELECT * FROM Users\nvar adults1 = allUsers.Where(u => u.Age >= 18).ToList();\n\n// Opção 2: Expression — filtramos no servidor\nvar adults2 = dbContext.Users.Where(u => u.Age >= 18).ToList(); // SELECT * FROM Users WHERE Age >= 18
\n\n

Resultados (aproximados, mas ilustrativos):

\n
    \n

Blogs

Book Recommendations