I know I can create expression trees using:
Factory methods.
Compiler conversion of a lambda expression to an Expression
.
For complicated expression trees I would prefer 2 because it is more concise.
Is it possible to refer to already constructed Expressions
using 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));
}
}
There isn't anything out of the box, but you can build a tool yourself to provide this functionality.
You can write a method that accepts an expression that has two parameters, one "real" parameter and one parameter of some value that you want to substitute with the value of another expression. You can then have an expression that resolves to that value and replace all instances of the parameter with the 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);
}
}
You can then 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);
}
You can't mix arbitrary Expression
instances with compile-time expression trees. What you can do is construct a new expression tree with specific nodes replaced, so you can have i => i + marker
and then construct a new tree with the marker
node replaced by your runtime expression. This requires writing an 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();
}
The slightly cryptic expression
Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()()
can be explained by observing that the compiler will capture the expression we're inserting in a closure, regardless of where it came from, so the argument of the method call is always a reference to this closure that we need to evaluate to get at the actual expression.
This scales to an arbitrary number of replacement expressions with a minimum of explicit typing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With