Comment compiler un arbre d'expression en une méthode appelable, C #?
-
19-08-2019 - |
Question
J'ai créé un arbre d'expression en analysant un fichier XML en utilisant la classe d'expression en C #. Voir cette question .
Je n'ai que les options Ajouter, Soustraire, Diviser, Multiplier, Paramètres, Et et Ou dans mon arbre d'expression. Existe-t-il un moyen de convertir cette ExpressionTree en une méthode appelable? ... ou dois-je émettre l'IL manuellement?
Cordialement,
La solution
Voici un exemple des deux approches. Si j'ai oublié quelque chose ou si vous souhaitez plus d'informations, faites le moi savoir.
static void Main()
{
// try to do "x + (3 * x)"
var single = BuildSingle<decimal>();
var composite = BuildComposite<decimal>();
Console.WriteLine("{0} vs {1}", single(13.2M), composite(13.2M));
}
// utility method to get the 3 as the correct type, since there is not always a "int x T"
static Expression ConvertConstant<TSource, TDestination>(TSource value)
{
return Expression.Convert(Expression.Constant(value, typeof(TSource)), typeof(TDestination));
}
// option 1: a single expression tree; this is the most efficient
static Func<T,T> BuildSingle<T>()
{
var param = Expression.Parameter(typeof(T), "x");
Expression body = Expression.Add(param, Expression.Multiply(
ConvertConstant<int, T>(3), param));
var lambda = Expression.Lambda<Func<T, T>>(body, param);
return lambda.Compile();
}
// option 2: nested expression trees:
static Func<T, T> BuildComposite<T>()
{
// step 1: do the multiply:
var paramInner = Expression.Parameter(typeof(T), "inner");
Expression bodyInner = Expression.Multiply(
ConvertConstant<int, T>(3), paramInner);
var lambdaInner = Expression.Lambda(bodyInner, paramInner);
// step 2: do the add, invoking the existing tree
var paramOuter = Expression.Parameter(typeof(T), "outer");
Expression bodyOuter = Expression.Add(paramOuter, Expression.Invoke(lambdaInner, paramOuter));
var lambdaOuter = Expression.Lambda<Func<T, T>>(bodyOuter, paramOuter);
return lambdaOuter.Compile();
}
Personnellement, je viserais la première méthode. c'est à la fois plus simple et plus efficace. Cela peut impliquer de passer le paramètre original à travers une pile de code imbriqué, mais qu’il en soit ainsi. J'ai un code quelque part qui prend les "Invoke". approche (composite), et ré-écrit l’arbre comme première approche (single) - mais il est assez complexe et long. Mais très utile pour Entity Framework (qui ne supporte pas Expression.Invoke).
Autres conseils
Vous devez créer un lambda - c.-à-d.
var lambda = Expression.Lambda<Func<float,int>>(body, param);
Func<float,int> method = lambda.Compile();
int v = method(1.0); // test
où " corps " est votre arbre d’expression (prenant un float, retournant un int) impliquant le paramètre ParameterExpression
.