1

I know how to replace a parameter with ExpressionVisitor but I was wondering if there's a way to remove a parameter from a Expression.Block.

Ideally I should crawl the entire Expression tree and remove the parameter every time it is declared inside a Block.

Any idea how to do that with ExpressionVisitor?

Mauro Sampietro
  • 2,739
  • 1
  • 24
  • 50
  • Can you give an example of what you want to obtain? If you have `(x, y) => x + y + 1`, what does it mean removing y? Do you want `x => x +1`? But what happens if you have `(x, y) => Math.Max(x, y)`? Or even more complex expressions... You can't remove a parameter and leave an hole – xanatos Mar 30 '17 at 07:57
  • This is the scenario. My expression builder declares a list. This expression builder is used recursively an so, many lists are declared (this way various items are inserted in their 'local' list). By removing inner declarations of the list in and replacing the parameter with an ExpressionVisitor i could redirect all the insertion in a unique list (the one declared by the first call to my expression builder). – Mauro Sampietro Mar 30 '17 at 09:50
  • So replace the `new List<>` (`an ExpressionType.New`) with the parameter representing the "master" list). So you'll have something like: `var localList = masterList; localList.Add(something)` This question is too much specific to be able to be solved without a concrete example. – xanatos Mar 30 '17 at 09:53
  • Aside, from my specific case. The question is how to remove, instead of replacing, a param. This question is not specific in that regard. – Mauro Sampietro Mar 30 '17 at 11:42
  • By param you mean the local variable of a Block? (so `Expression.Block(variable1, variable2, expression1, expression2)` you want to remove `variable1` (and/or `variable2`)? – xanatos Mar 30 '17 at 12:09
  • Exactly. At the moment i found this solution: walk the tree, search for the Block and cast it to BlockExpression. The Expressions property contains the sub-block parts of the expression. Create a new Expression.Block skipping the first part. – Mauro Sampietro Mar 30 '17 at 12:26

1 Answers1

1

A simple class to remove local variables from a BlockExpression and replace them with whatever you want.

public class BlockVariableRemover : ExpressionVisitor
{
    private readonly Dictionary<Expression, Expression> replaces = new Dictionary<Expression, Expression>();

    public readonly Func<ParameterExpression, int, Expression> Replacer;

    public BlockVariableRemover(Func<ParameterExpression, int, Expression> replacer)
    {
        Replacer = replacer;
    }

    protected override Expression VisitBlock(BlockExpression node)
    {
        var removed = new List<Expression>();

        var variables = node.Variables.ToList();

        for (int i = 0; i < variables.Count; i++)
        {
            var variable = variables[i];
            var to = Replacer(variable, i);

            if (to != variable)
            {
                removed.Add(variable);
                replaces.Add(variable, to);
                variables.RemoveAt(i);
                i--;
            }
        }

        if (removed.Count == 0)
        {
            return base.VisitBlock(node);
        }

        var expressions = node.Expressions.ToArray();

        for (int i = 0; i < expressions.Length; i++)
        {
            expressions[i] = Visit(expressions[i]);
        }

        foreach (var rem in removed)
        {
            replaces.Remove(rem);
        }

        return Expression.Block(variables, expressions);
    }

    public override Expression Visit(Expression node)
    {
        Expression to;

        if (node != null && replaces.TryGetValue(node, out to))
        {
            return base.Visit(to);
        }

        return base.Visit(node);
    }
}

Use it like:

Expression<Func<int, int>> exp;

{
    var var1 = Expression.Variable(typeof(int), "var1");
    var var2 = Expression.Variable(typeof(long), "var2");

    var par1 = Expression.Parameter(typeof(int), "par1");

    var block = Expression.Block(new[] { var1, var2 }, Expression.Increment(var1));

    exp = Expression.Lambda<Func<int, int>>(block, par1);

    // Test
    var compiled = exp.Compile();
    Console.WriteLine(compiled(10));
}

// Begin replace
{
    var par1 = exp.Parameters[0];

    var block2 = new BlockVariableRemover(
        // ix is the index of the variable, 
        // return x if you don't want to modify,
        // return whatever you want (even Expression.Empty()) to do 
        // a replace
        (x, ix) => ix == 0 && x.Type == typeof(int) ? par1 : x)
        .Visit(exp.Body);

    // Final result
    var exp2 = Expression.Lambda<Func<int, int>>(block2, par1);

    // Test
    var compiled = exp2.Compile();
    Console.WriteLine(compiled(10));
}
xanatos
  • 109,618
  • 12
  • 197
  • 280