C#
- Expression Trees
![]() |
Neste artigo vamos recordar o que é e como funcionam as Expression Trees no C# |
As Expression Trees ou Árvores de Expressão, são recurso exclusivo do C#, que fornecem uma maneira de representar o código como uma estrutura de dados. Em alto nível, eles permitem que os desenvolvedores tratem o código como dados, permitindo a geração dinâmica e a manipulação do código durante o tempo de execução. Esse recurso é especialmente útil em cenários como a criação de consultas LINQ dinâmicas ou a criação de delegados compilados em tempo de execução.
Como funcionam as Expression Trees
Em vez de compilar uma expressão lambda diretamente em código executável, as Expression Trees representam a estrutura lógica da expressão em uma forma de árvore. Cada nó na árvore representa uma operação ou parte da expressão.
Essa representação permite que você manipule e analise as expressões em tempo de execução. Você pode criar, modificar e até mesmo combinar diferentes expressões para formar novas expressões.
Este recurso é amplamente utilizadas no contexto de LINQ (Language Integrated Query), onde são usadas para traduzir expressões lambda em consultas SQL ou outras formas de consulta específicas do provedor de dados.
Exemplo básico: Vamos criar um exemplo onde utilizaremos Expression Trees para construir uma expressão que representa a soma de dois números.
Para isso vamos criar um projeto Console no .NET 8 chamado Cshp_ExpTrees usando o recurso Top Level Statement.
using
System.Linq.Expressions; Console.WriteLine( "\nUsando Expression Trees");Console.WriteLine("Para representar a soma de dois números #\n"); Console.WriteLine("a e b com valor 5 e 10\n"); ParameterExpression paramA = Expression.Parameter( typeof(int), "a");ParameterExpression paramB = Expression.Parameter(typeof(int), "b"); // Corpo da expressão: a + b BinaryExpression body = Expression.Add(paramA, paramB); // Criando a expressão lambda Expression<Func<int, int, int>> soma = Expression.Lambda<Func<int, int, int>>(body, paramA, paramB); // Compilando e executando a expressão Func<int, int, int> functionSoma = soma.Compile(); int resultado = functionSoma(5, 10);// Exibindo o resultado Console.WriteLine("Resultado da soma: " + resultado); |
O básico
As expressões lambdas no C# são uma forma conscisa de representar métodos anônimos. Exemplo:
Func<int, int, int>
add = (a, b) => a + b;
|
Porém quando desejamo inspecionar ou manipular a própria expressão lambda usamos árvores de expressão , assim:
|
Expression<Func<int,
int, int>>
addExpression = (a, b) => a + b;
|
A principal diferença é o invólucro Expression<> que informa ao compilador para tratar o lambda com um dados e não como código executável.
Usando esta lógica podemos criar árvore de expressões manualmente usando os métodos Factory da classe conforme o exemplo a seguir:
var
paramA = Expression.Parameter(typeof(int),
"a"); var paramB = Expression.Parameter(typeof(int), "b"); var body = Expression.Add(paramA, paramB);var addExpression = Expression.Lambda<Func<int, int, int>>(body, paramA, paramB);var compiled = addExpression.Compile();Console.WriteLine(compiled(1, 2)); |
Neste exemplo criamos uma árvore de expressão fizemoa a compilação par aum delegate e executamos.
Exemplos práticos
Vamos criar um exemplo prático usando Expression Trees e LINQ para construir uma consulta dinâmica que filtra uma lista de objetos com base em critérios específicos.
Neste exemplo, consideraremos uma classe Pessoa com propriedades como Nome, Idade e Cidade. Vamos criar uma consulta LINQ que filtra pessoas com base em critérios dinâmicos.
Em um projeto console - Cshp_ExpTrees - vamos criar uma classe Pessoa :
class
Pessoa { public string? Nome { get; set; } public int Idade { get; set; } public string? Cidade { get; set; } } |
A seguir na classe Program vamos definir o código abaixo:
// Lista de pessoas using Cshp_ExpTrees; using System.Linq.Expressions; List<Pessoa> pessoas = new List<Pessoa>{ new Pessoa { Nome = "Alice", Idade = 25, Cidade = "São Paulo" }, new Pessoa { Nome = "Maria", Idade = 30, Cidade = "Rio de Janeiro" }, new Pessoa { Nome = "Carlos", Idade = 22, Cidade = "Campinas" }, new Pessoa { Nome = "Amanda", Idade = 22, Cidade = "Santos" }, new Pessoa { Nome = "Silvia", Idade = 22, Cidade = "Salvador" } }; Console.WriteLine( "Clientes\n");foreach (var pessoa in pessoas) Console.WriteLine($"{pessoa.Nome} \t {pessoa.Cidade}"); // Critérios de filtragem (dinâmicos) var criterioFiltro = BuildFilter<Pessoa>("Cidade", "São Paulo"); // Aplicando a consulta LINQ com Expression Trees var pessoasFiltradas = pessoas.AsQueryable().Where(criterioFiltro).ToList(); // Exibindo os resultados Console.WriteLine("\nCliente(s) filtrado(s):"); foreach (var pessoa in pessoasFiltradas){ Console.WriteLine($"Nome: {pessoa.Nome}, Idade: {pessoa.Idade}, Cidade: {pessoa.Cidade}"); } Console.ReadLine(); static Expression<Func<T, bool>> BuildFilter<T>(string propertyName, object value){ var parameter = Expression.Parameter(typeof(T), "x"); var property = Expression.Property(parameter, propertyName); var constant = Expression.Constant(value); var equal = Expression.Equal(property, constant); return Expression.Lambda<Func<T, bool>>(equal, parameter); } |
Entendendo o código:
Executando teremos o resultado abaixo:
Além disso podemos
usar
classe ExpressionVisitor que fornece uma
maneira de percorrer e modificar uma árvore de expressão. Ele usa o padrão
Visitor, onde um método separado é chamado
para cada tipo de nó na árvore.
Aqui está um exemplo simples de ExpressionVisitor
que substitui todas as instâncias de um parâmetro específico por uma expressão
constante:
using
System.Linq.Expressions; Expression<Func< int, int>> expr = x => x * 100;Console.WriteLine("Expressão original : x * 100 "); var replacer = new ParameterReplacer(expr.Parameters[0], Expression.Constant(100));var novaExpressao = replacer.Visit(expr.Body) as BinaryExpression; Console.WriteLine( "\nExpressão com parâmetro substituido");Console.WriteLine(novaExpressao); // saida: (100 * 100) Console.ReadLine(); public class ParameterReplacer : ExpressionVisitor { private readonly ParameterExpression _oldParameter; private readonly Expression _replacement; public ParameterReplacer(ParameterExpression oldParameter, Expression replacement) { _oldParameter = oldParameter; _replacement = replacement; } protected override Expression VisitParameter(ParameterExpression node) { if (node == _oldParameter) return _replacement; return base.VisitParameter(node); } } |
Resultado:
As árvores de expressão são uma ferramenta poderosa no arsenal do desenvolvedor C#, permitindo a geração e manipulação dinâmica de código. Ao compreender e aproveitar seus recursos, você poderá criar aplicativos mais flexíveis e dinâmicos.
Pegue o código do projeto aqui: Cshp_ExpTrees.zip
"(Disse Jesus) Quem crê no Filho de Deus, em si mesmo tem
o testemunho; quem a Deus não crê mentiroso o fez, porquanto não creu no
testemunho que Deus de seu Filho deu"
1 João 5:10
Referências:
C# - Tasks x Threads. Qual a diferença
DateTime - Macoratti.net
Null o que é isso ? - Macoratti.net
Formatação de data e hora para uma cultura ...
C# - Calculando a diferença entre duas datas
NET - Padrão de Projeto - Null Object Pattern
C# - Fundamentos : Definindo DateTime como Null ...
C# - Os tipos Nullable (Tipos Anuláveis)