Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O compilador C# criou todas as árvores de expressão que você viu até agora. Você criou uma expressão lambda atribuída a uma variável digitada como um Expression<Func<T>> ou algum tipo semelhante. Para muitos cenários, você cria uma expressão na memória durante o tempo de execução.
Árvores de expressão são imutáveis. Ser imutável significa que você deve construir a árvore a partir das folhas até a raiz. As APIs que você usa para criar as árvores de expressão refletem esse fato: os métodos que você usa para criar um nó usam todos os filhos como argumentos. Vamos percorrer alguns exemplos para mostrar as técnicas.
Criar nós
Comece com a expressão de adição com a qual você tem trabalhado ao longo destas seções:
Expression<Func<int>> sum = () => 1 + 2;
Para construir essa árvore de expressão, você precisará criar os nós folha. Os nós folha são constantes. Use o Constant método para criar os nós:
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
Em seguida, crie a expressão de adição:
var addition = Expression.Add(one, two);
Depois de criar a expressão de adição, crie a expressão lambda:
var lambda = Expression.Lambda(addition);
Essa expressão lambda não contém argumentos. Mais adiante nesta seção, você verá como mapear argumentos para parâmetros e criar expressões mais complicadas.
Para expressões como essa, você pode combinar todas as chamadas em uma só instrução:
var lambda2 = Expression.Lambda(
Expression.Add(
Expression.Constant(1, typeof(int)),
Expression.Constant(2, typeof(int))
)
);
Criar uma árvore
A seção anterior mostrava as noções básicas da criação de uma árvore de expressão na memória. Árvores mais complexas geralmente significam mais tipos de nós e um número maior de nós na árvore. Vamos percorrer mais um exemplo e mostrar dois outros tipos de nó que você normalmente criará quando criar árvores de expressão: os nós de argumento e os nós de chamada de método. Vamos criar uma árvore de expressão para criar esta expressão:
Expression<Func<double, double, double>> distanceCalc =
(x, y) => Math.Sqrt(x * x + y * y);
Você começa criando expressões de parâmetro para x e y.
var xParameter = Expression.Parameter(typeof(double), "x");
var yParameter = Expression.Parameter(typeof(double), "y");
A criação das expressões de multiplicação e adição segue o padrão que você já viu:
var xSquared = Expression.Multiply(xParameter, xParameter);
var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);
Em seguida, você precisa criar uma expressão de chamada de método para a chamada para Math.Sqrt.
var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) }) ?? throw new InvalidOperationException("Math.Sqrt not found!");
var distance = Expression.Call(sqrtMethod, sum);
A GetMethod chamada poderá ser retornada null se o método não for encontrado. Provavelmente isso ocorre porque você errou a ortografia do nome do método. Caso contrário, isso pode significar que o assembly necessário não está carregado. Por fim, você coloca a chamada de método em uma expressão lambda e define os argumentos para a expressão lambda:
var distanceLambda = Expression.Lambda(
distance,
xParameter,
yParameter);
Neste exemplo mais complicado, você verá mais algumas técnicas que geralmente precisam para criar árvores de expressão.
Primeiro, você precisa criar os objetos que representam parâmetros ou variáveis locais antes de usá-los. Depois de criar esses objetos, você pode usá-los na árvore de expressão onde precisar.
Em segundo lugar, você precisa usar um subconjunto das APIs de Reflexão para criar um System.Reflection.MethodInfo objeto para que você possa criar uma árvore de expressão para acessar esse método. Você deve limitar-se ao subconjunto das APIs de Reflexão que estão disponíveis na plataforma .NET Core. Novamente, essas técnicas se estendem a outras árvores de expressão.
Criar código em profundidade
Você não está limitado no que pode criar usando essas APIs. No entanto, quanto mais complicada a árvore de expressão que você deseja construir, mais difícil é gerenciar e ler o código.
Vamos criar uma árvore de expressão equivalente a este código:
Func<int, int> factorialFunc = (n) =>
{
var res = 1;
while (n > 1)
{
res = res * n;
n--;
}
return res;
};
O código anterior não constrói a árvore de expressão, mas simplesmente o delegado. Usando a classe Expression, não é possível criar lambdas de instrução. Este é o código necessário para criar a mesma funcionalidade. Não há uma API para criar um while loop, em vez disso, você precisa criar um loop que contenha um teste condicional e um destino de rótulo para sair do loop.
var nArgument = Expression.Parameter(typeof(int), "n");
var result = Expression.Variable(typeof(int), "result");
// Creating a label that represents the return value
LabelTarget label = Expression.Label(typeof(int));
var initializeResult = Expression.Assign(result, Expression.Constant(1));
// This is the inner block that performs the multiplication,
// and decrements the value of 'n'
var block = Expression.Block(
Expression.Assign(result,
Expression.Multiply(result, nArgument)),
Expression.PostDecrementAssign(nArgument)
);
// Creating a method body.
BlockExpression body = Expression.Block(
new[] { result },
initializeResult,
Expression.Loop(
Expression.IfThenElse(
Expression.GreaterThan(nArgument, Expression.Constant(1)),
block,
Expression.Break(label, result)
),
label
)
);
O código para criar a árvore de expressão para a função fatorial é um pouco mais longo, mais complicado e está cheio de rótulos e instruções de quebra e outros elementos que você gostaria de evitar em nossas tarefas de codificação diárias.
Para esta seção, você escreveu código para visitar todos os nós nesta árvore de expressão e registrar informações sobre os nós que são gerados neste exemplo. Você pode exibir ou baixar o código de exemplo no repositório GitHub do dotnet/docs. Experimente por si mesmo criando e executando os exemplos.
Mapear construções de código para expressões
O exemplo de código a seguir demonstra uma árvore de expressão que representa a expressão num => num < 5 lambda usando a API.
// Manually build the expression tree for
// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
numLessThanFive,
new ParameterExpression[] { numParam });
A API de árvores de expressão também dá suporte a atribuições e expressões de fluxo de controle, como loops, blocos condicionais e try-catch blocos. Usando a API, você pode criar árvores de expressão mais complexas do que aquelas que podem ser criadas a partir de expressões lambda pelo compilador C#. O exemplo a seguir demonstra como criar uma árvore de expressão que calcula o fatorial de um número.
// Creating a parameter expression.
ParameterExpression value = Expression.Parameter(typeof(int), "value");
// Creating an expression to hold a local variable.
ParameterExpression result = Expression.Parameter(typeof(int), "result");
// Creating a label to jump to from a loop.
LabelTarget label = Expression.Label(typeof(int));
// Creating a method body.
BlockExpression block = Expression.Block(
// Adding a local variable.
new[] { result },
// Assigning a constant to a local variable: result = 1
Expression.Assign(result, Expression.Constant(1)),
// Adding a loop.
Expression.Loop(
// Adding a conditional block into the loop.
Expression.IfThenElse(
// Condition: value > 1
Expression.GreaterThan(value, Expression.Constant(1)),
// If true: result *= value --
Expression.MultiplyAssign(result,
Expression.PostDecrementAssign(value)),
// If false, exit the loop and go to the label.
Expression.Break(label, result)
),
// Label to jump to.
label
)
);
// Compile and execute an expression tree.
int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);
Console.WriteLine(factorial);
// Prints 120.
Para obter mais informações, consulte Gerando métodos dinâmicos com árvores de expressão no Visual Studio 2010, que também se aplica a versões posteriores do Visual Studio.