Using an expression in an expression tree generated by the compiler

I know that I can create expression trees using:

  • Factory.

  • Convert lambda expression compiler to Expression .

For complex expression trees, I would prefer 2 because it is more concise.

Can I refer to already built Expressions in this way?

 using System; using System.Linq.Expressions; public class Test { public static Expression<Func<int, int>> Add(Expression expr) { #if false // works ParameterExpression i = Expression.Parameter(typeof(int)); return Expression.Lambda<Func<int, int>>(Expression.Add(i, expr), i); #else // compiler error, can I pass expr here somehow? return i => i + expr; #endif } public static void Main() { Func<int, int> f = Add(Expression.Constant(42)).Compile(); Console.WriteLine(f(1)); } } 
+5
source share
2 answers

You cannot mix arbitrary instances of Expression with compilation-time expression trees. You can create a new expression tree with replaced nodes, so you can have i => i + marker , and then build a new tree with replacing the marker node with your expression at run time. To do this, write the appropriate ExpressionVisitor :

 public static class ExpressionExtensions { public static T AsPlaceholder<T>(this Expression expression) { throw new InvalidOperationException( "Expression contains placeholders." ); } public static Expression FillPlaceholders(this Expression expression) { return new PlaceholderExpressionVisitor().Visit(expression); } } class PlaceholderExpressionVisitor : ExpressionVisitor { protected override Expression VisitMethodCall(MethodCallExpression node) { if ( node.Method.DeclaringType == typeof(ExpressionExtensions) && node.Method.Name == "AsPlaceholder" // in C# 6, we would use nameof() ) { return Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()(); } else { return base.VisitMethodCall(node); } } } 

Add now becomes:

 public static Expression<Func<int, int>> Add(Expression expr) { Expression<Func<int, int>> add = i => i + expr.AsPlaceholder<int>(); return (Expression<Func<int, int>>) add.FillPlaceholders(); } 

Somewhat cryptic expression

 Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()() 

can be explained by noting that the compiler will commit the expression that we insert into the closure, regardless of where it came from, so the argument to the method call is always a reference to this closure, which we need to evaluate to get the actual expression.

This scales to an arbitrary number of replacement expressions with a minimal explicit character set.

+2
source

There is nothing out of the box, but you can create your own tool to provide this functionality.

You can write a method that takes an expression that has two parameters, one β€œreal” parameter and one parameter of some value that you want to replace with the value of another expression. Then you can have an expression that resolves this value and replaces all instances of the parameter with a second expression:

 public static Expression<Func<TSource, TResult>> BuildExpression <TSource, TOther, TResult>( Expression<Func<TSource, TOther, TResult>> function, Expression<Func<TOther>> innerExpression) { var body = function.Body.Replace(function.Parameters[1], innerExpression.Body); return Expression.Lambda<Func<TSource, TResult>>(body, function.Parameters[0]); } 

You can use the following method to replace all instances of one expression with another:

 public static Expression Replace(this Expression expression, Expression searchEx, Expression replaceEx) { return new ReplaceVisitor(searchEx, replaceEx).Visit(expression); } internal class ReplaceVisitor : ExpressionVisitor { private readonly Expression from, to; public ReplaceVisitor(Expression from, Expression to) { this.from = from; this.to = to; } public override Expression Visit(Expression node) { return node == from ? to : base.Visit(node); } } 

Then you can apply it to your case as follows:

 public static Expression<Func<int, int>> Add(Expression<Func<int>> expr) { return BuildExpression((int i, int n) => i + n, expr); } 
+7
source

Source: https://habr.com/ru/post/1207140/


All Articles