8

If I have an expression of a function delegate that takes a number of parameters like so:

Expression<Func<int, int, int, bool>> test = (num1, num2, num3) => num1 + num2 == num3;

is there a way / how can I substitute one of the values (say 5 for num1) and get the equivalent expression to:

Expression<Func<int, int, bool>> test = (num2, num3) => 5 + num2 == num3;

EDIT:

Also needs to resolve complex types, e.g.:

    Expression<Func<Thing, int, int>> test = (thing, num2) => thing.AnIntProp + num2;
Simon C
  • 9,458
  • 3
  • 36
  • 55
  • possible duplicate of [C# Linq vs. Currying](http://stackoverflow.com/questions/8826266/c-sharp-linq-vs-currying) – Tim S. Jul 31 '13 at 01:27
  • 2
    while I voted it is dup, this question may actually be different. Maybe you are looking for usage of expression visitor to substitute argument with value? (something along the lines of [this](http://stackoverflow.com/questions/11164009/using-a-linq-expressionvisitor-to-replace-primitive-parameters-with-property-ref) ) – Alexei Levenkov Jul 31 '13 at 02:03
  • Not quite. I want to be working with expressions. I think it is closer to [this](http://stackoverflow.com/questions/11159697/replace-parameter-in-lambda-expression) or [this](http://stackoverflow.com/questions/5631070/currying-expressions-in-c-sharp) – Simon C Jul 31 '13 at 03:44

1 Answers1

3

My answer was to use a an expression visitor. (thanks to @Alexei-levenkov for pointing it out).

The answer for my particular situation was a little different than for the simplified example I used in the question. But, for completeness, here was how I did it:

public class ResolveParameterVisitor : ExpressionVisitor
{
    private readonly ParameterExpression _param;
    private readonly object _value;

    public ResolveParameterVisitor(ParameterExpression param, object value)
    {
        _param = param;
        _value = value;
    }

    public Expression ResolveLocalValues(Expression exp)
    {
        return Visit(exp);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type == _param.Type && node.Name == _param.Name
            && node.Type.IsSimpleType())
        {
            return Expression.Constant(_value);
        }

            return base.VisitParameter(node);
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        var parameters = node.Parameters.Where(p => p.Name != _param.Name && p.Type != _param.Type).ToList();
        return Expression.Lambda(Visit(node.Body), parameters);
    }
}

Note that IsSimpleType is an extension I borrowed from this gist by jonothanconway.

In my situation I wanted to replace use of a a complex type. e.g:

Expression<Func<Thing, int, bool>> test = (thing, num) => thing.AnIntProperty == num;

So I did an override of the VisitMember method. This is still a work in progress but looks like this:

      protected override Expression VisitMember(MemberExpression m)
    {
        if (m.Expression != null
            && m.Expression.NodeType == ExpressionType.Parameter
            && m.Expression.Type == _param.Type && ((ParameterExpression)m.Expression).Name == _param.Name)
        {
            object newVal;
            if (m.Member is FieldInfo)
                newVal = ((FieldInfo)m.Member).GetValue(_value);
            else if (m.Member is PropertyInfo)
                newVal = ((PropertyInfo)m.Member).GetValue(_value, null);
            else
                newVal = null;
            return Expression.Constant(newVal);
        }

        return base.VisitMember(m);
    }

This will only resolve a field or property. Next step might be to add support for a method (but as they have params themselves, so that'll take some more work...)

EDIT: The above Member visitor solution also wouldn't support passing an Object itself into a method call. e.g. (x, thing) => x.DoSomething(thing) so a modification would be needed to do that also.

Simon C
  • 9,458
  • 3
  • 36
  • 55
  • I was able to use this successfully. However, I bumped into an issue with nullable types when resolving to a non-null value in a comparison expression. During resolving here, the constant expression infers the data type as, for example, `DateTime` rather than `DateTime?`. To get around this, I explicitly pass the type: `return Expression.Constant(newVal, m.Type);`. This keeps it the same type as it originally was. – bzier May 21 '18 at 23:30