Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Une arborescence d’expressions est une structure de données qui représente du code. Ce n'est pas du code compilé et exécutable. Si vous souhaitez exécuter le code .NET représenté par une arborescence d’expressions, vous devez le convertir en instructions IL exécutables. L’exécution d’une arborescence d’expressions peut retourner une valeur, ou elle peut simplement effectuer une action telle que l’appel d’une méthode.
Seules les arborescences d’expressions qui représentent des expressions lambda peuvent être exécutées. Les arborescences d’expressions qui représentent des expressions lambda sont de type LambdaExpression ou Expression<TDelegate>. Pour exécuter ces arborescences d’expressions, appelez la Compile méthode pour créer un délégué exécutable, puis appelez le délégué.
Remarque
Si le type du délégué n’est pas connu, autrement dit, l’expression lambda est de type LambdaExpression et non Expression<TDelegate>, appelez la DynamicInvoke méthode sur le délégué au lieu de l’appeler directement.
Si une arborescence d’expressions ne représente pas d’expression lambda, vous pouvez créer une expression lambda qui a l’arborescence d’expressions d’origine comme corps, en appelant la Lambda<TDelegate>(Expression, IEnumerable<ParameterExpression>) méthode. Ensuite, vous pouvez exécuter l’expression lambda comme décrit précédemment dans cette section.
Expressions lambda vers fonctions
Vous pouvez convertir n’importe quel type LambdaExpression ou tout type dérivé de LambdaExpression en il exécutable. D’autres types d’expressions ne peuvent pas être directement convertis en code. Cette restriction a peu d’effet dans la pratique. Les expressions lambda sont les seuls types d’expressions que vous souhaitez exécuter en convertissant en langage intermédiaire exécutable (IL). (Pensez à ce qu’il signifierait d’exécuter directement un System.Linq.Expressions.ConstantExpression. Cela signifierait-il quelque chose d’utile ?) Toute arborescence d’expressions qui est un System.Linq.Expressions.LambdaExpression, ou un type dérivé de LambdaExpression
peut être converti en il. Le type System.Linq.Expressions.Expression<TDelegate> d’expression est le seul exemple concret dans les bibliothèques .NET Core. Il est utilisé pour représenter une expression qui est mappée à n’importe quel type de délégué. Étant donné que ce type est mappé à un type délégué, .NET peut examiner l’expression et générer l’il pour un délégué approprié qui correspond à la signature de l’expression lambda. Le type délégué est basé sur le type d’expression. Si vous souhaitez utiliser l’objet délégué d’une manière fortement typée, vous devez connaître le type de retour et la liste d’arguments. La LambdaExpression.Compile()
méthode retourne le Delegate
type. Vous devez le convertir en type délégué approprié pour que les outils de compilation vérifient la liste d’arguments ou le type de retour.
Dans la plupart des cas, un mappage simple entre une expression et son délégué correspondant existe. Par exemple, une arborescence d’expressions représentée par Expression<Func<int>>
serait convertie en délégué du type Func<int>
. Pour une expression lambda avec n’importe quel type de retour et liste d’arguments, il existe un type délégué qui est le type cible du code exécutable représenté par cette expression lambda.
Le System.Linq.Expressions.LambdaExpression type contient LambdaExpression.Compile et LambdaExpression.CompileToMethod les membres que vous utiliseriez pour convertir une arborescence d’expressions en code exécutable. La Compile
méthode crée un délégué. La méthode CompileToMethod
met à jour un objet System.Reflection.Emit.MethodBuilder avec le langage intermédiaire qui représente la sortie compilée de l’arborescence d’expressions.
Importante
CompileToMethod
est disponible uniquement dans .NET Framework, pas dans .NET Core ou .NET 5 et versions ultérieures.
Si vous le souhaitez, vous pouvez également fournir un System.Runtime.CompilerServices.DebugInfoGenerator qui reçoit les informations de débogage de symbole pour l’objet délégué généré.
DebugInfoGenerator
fournit des informations de débogage complètes sur le délégué généré.
Vous devez convertir une expression en délégué à l’aide du code suivant :
Expression<Func<int>> add = () => 1 + 2;
var func = add.Compile(); // Create Delegate
var answer = func(); // Invoke Delegate
Console.WriteLine(answer);
L’exemple de code suivant illustre les types concrets utilisés lors de la compilation et de l’exécution d’une arborescence d’expressions.
Expression<Func<int, bool>> expr = num => num < 5;
// Compiling the expression tree into a delegate.
Func<int, bool> result = expr.Compile();
// Invoking the delegate and writing the result to the console.
Console.WriteLine(result(4));
// Prints True.
// You can also use simplified syntax
// to compile and run an expression tree.
// The following line can replace two previous statements.
Console.WriteLine(expr.Compile()(4));
// Also prints True.
L’exemple de code suivant montre comment exécuter une arborescence d’expressions qui représente l’élévation d’un nombre à une puissance en créant une expression lambda et en l’exécutant. Le résultat, qui représente le nombre élevé à la puissance, est affiché.
// The expression tree to execute.
BinaryExpression be = Expression.Power(Expression.Constant(2d), Expression.Constant(3d));
// Create a lambda expression.
Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);
// Compile the lambda expression.
Func<double> compiledExpression = le.Compile();
// Execute the lambda expression.
double result = compiledExpression();
// Display the result.
Console.WriteLine(result);
// This code produces the following output:
// 8
Exécution et durées de vie
Vous exécutez le code en appelant le délégué créé lorsque vous avez appelé LambdaExpression.Compile()
. Le code précédent, , add.Compile()
retourne un délégué. Vous appelez ce délégué en appelant func()
, qui exécute le code.
Ce délégué représente le code dans l’arborescence d’expressions. Vous pouvez conserver le handle de ce délégué et l’appeler ultérieurement. Vous n’avez pas besoin de compiler l’arborescence d’expressions chaque fois que vous souhaitez exécuter le code qu’il représente. (N’oubliez pas que les arborescences d’expressions sont immuables et que la compilation de la même arborescence d’expressions crée ultérieurement un délégué qui exécute le même code.)
Avertissement
Ne créez pas de mécanismes de mise en cache plus sophistiqués pour augmenter les performances en évitant les appels de compilation inutiles. La comparaison de deux arborescences d’expressions arbitraires pour déterminer s’ils représentent le même algorithme est une opération fastidieuse. Le temps de calcul que vous économisez en évitant tout appel supplémentaire à LambdaExpression.Compile()
est probablement plus que compensé par le temps d'exécution du code qui détermine si deux arbres d'expressions différents aboutissent au même code exécutable.
Mises en garde
Compiler une expression lambda en un délégué et appeler ce délégué sont l’une des opérations les plus simples que vous puissiez effectuer avec une arborescence d’expressions. Toutefois, même avec cette opération simple, il existe des mises en garde dont vous devez être conscient.
Les expressions lambda créent des fermetures sur toutes les variables locales référencées dans l’expression. Vous devez garantir que toutes les variables qui font partie du délégué sont utilisables à l’emplacement où vous appelez Compile
, et lorsque vous exécutez le délégué résultant. Le compilateur garantit que les variables sont dans la portée. Toutefois, si votre expression accède à une variable qui implémente IDisposable
, il est possible que votre code puisse supprimer l’objet alors qu’il est toujours détenu par l’arborescence d’expressions.
Par exemple, ce code fonctionne correctement, car int
n’implémente IDisposable
pas :
private static Func<int, int> CreateBoundFunc()
{
var constant = 5; // constant is captured by the expression tree
Expression<Func<int, int>> expression = (b) => constant + b;
var rVal = expression.Compile();
return rVal;
}
Le délégué a capturé une référence à la variable constant
locale. Cette variable est accessible à tout moment plus tard, lorsque la fonction retournée par CreateBoundFunc
s’exécute.
Toutefois, considérez une classe (plutôt artificielle) qui implémente System.IDisposable.
public class Resource : IDisposable
{
private bool _isDisposed = false;
public int Argument
{
get
{
if (!_isDisposed)
return 5;
else throw new ObjectDisposedException("Resource");
}
}
public void Dispose()
{
_isDisposed = true;
}
}
Si vous l’utilisez dans une expression comme indiqué dans le code suivant, vous obtenez une System.ObjectDisposedException fois que vous exécutez le code référencé par la Resource.Argument
propriété :
private static Func<int, int> CreateBoundResource()
{
using (var constant = new Resource()) // constant is captured by the expression tree
{
Expression<Func<int, int>> expression = (b) => constant.Argument + b;
var rVal = expression.Compile();
return rVal;
}
}
Le délégué retourné par cette méthode a fermé l’objet constant
, qui a été supprimé. (Il a été supprimé car il a été déclaré dans une instruction using
.)
Désormais, quand vous exécuterez le délégué retourné par cette méthode, une ObjectDisposedException
est levée au moment de l’exécution.
Il semble étrange d’avoir une erreur d’exécution représentant un concept de compilation, mais c’est le monde dans lequel vous entrez lorsque vous travaillez avec des arborescences d’expressions.
Il y a de nombreuses permutations de ce problème, donc il est difficile d’offrir des conseils généraux pour l’éviter. Veillez à accéder aux variables locales lors de la définition d’expressions et veillez à accéder à l’état dans l’objet actuel (représenté par this
) lors de la création d’une arborescence d’expressions retournée via une API publique.
Le code de votre expression peut référencer des méthodes ou des propriétés dans d’autres assemblys. Cet assembly doit être accessible lorsque l’expression est définie, lorsqu’elle est compilée et lorsque le délégué résultant est appelé. Vous êtes confronté à un ReferencedAssemblyNotFoundException
dans les cas où il n’est pas présent.
Résumé
Les arborescences d’expressions qui représentent des expressions lambda peuvent être compilées pour créer un délégué que vous pouvez exécuter. Les arborescences d’expressions fournissent un mécanisme permettant d’exécuter le code représenté par une arborescence d’expressions.
L’arborescence d’expressions représente le code qui s’exécuterait pour toute construction donnée que vous créez. Tant que l’environnement où vous compilez et exécutez le code correspond à l’environnement où vous créez l’expression, tout fonctionne comme prévu. Quand cela ne se produit pas, les erreurs sont prévisibles et sont interceptées dans vos premiers tests de code à l’aide des arborescences d’expressions.