Search code examples
linqexpression-trees

Convert expression tree types


I've searched high an low of SO to find a solution for my problem.

I've found several answers for when it comes to simple expressions like

var exp1 Expression<Func<T, bool>> x => x.Name == "MyName"

But I'm having trouble when the expressions are like:

var exp1 Expression<Func<T, bool>> x => x.Category.Name == "Coupe"

For the simple ones, I am able to convert any expression from one type (T) to another (TT), I need to do it also in the other cases, more complex...

Anyone who can help with some pointers? Here is what I've got so far:

private class CustomVisitor<T> : ExpressionVisitor
{
private readonly ParameterExpression mParameter;

public CustomVisitor(ParameterExpression parameter)
{
    mParameter = parameter;
}

//this method replaces original parameter with given in constructor
protected override Expression VisitParameter(ParameterExpression node)
{
    return mParameter;
}
private int counter = 0;

/// <summary>
/// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression" />.
/// </summary>
/// <param name="node">The expression to visit.</param>
/// <returns>
/// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
/// </returns>
/// <exception cref="System.NotImplementedException"></exception>
protected override Expression VisitMember(MemberExpression node)
{
    counter++;
    System.Diagnostics.Debug.WriteLine("{0} - {1}", node.ToString(), counter);
    try
    {
        //only properties are allowed if you use fields then you need to extend
        // this method to handle them
        if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
            throw new NotImplementedException();

        //name of a member referenced in original expression in your 
        //sample Id in mine Prop
        var memberName = node.Member.Name;
        //find property on type T (=PersonData) by name
        var otherMember = typeof(T).GetProperty(memberName);
        //visit left side of this expression p.Id this would be p
        var inner = Visit(node.Expression);

        return Expression.Property(inner, otherMember);
    }
    catch (Exception ex)
    {
        return null;
    }
}
}

Utility method:

public static Expression<Func<TDestin, T>> ConvertTypesInExpression<TSource, TDestin, T>(Expression<Func<TSource, T>> source)
{
    var param = Expression.Parameter(typeof(TDestin));

    var body = new CustomVisitor<TDestin>(param).Visit(source.Body);

    Expression<Func<TDestin, T>> lambda = Expression.Lambda<Func<TDestin, T>>(body, param);

    return lambda;
}

And it's being used like this:

var changedFilter = ConvertTypesInExpression<ClientNotificationRuleDto, ClientNotificationRule, bool>(filterExpression);

So if anyone can help with some ideias or pointers, that would be great!


Solution

  • Analyze this test:

    class Replaced
    {
        public Inner Inner { get; set; }
    }
    
    class Inner
    {
        public string Name { get; set; }
    }
    
    class Replacing
    {
        public Inner Inner { get; set; }
    }
    
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            var parameter = Expression.Parameter(typeof(Replacing));
            var visitor = new CustomVisitor(parameter);
            Expression<Func<Replaced, bool>> expression = x => x.Inner.Name == "ss";
            var resultExpression = (Expression<Func<Replacing, bool>>)visitor.Visit(expression);
    
            var function = resultExpression.Compile();
            var result = function(new Replacing
             {
                 Inner = new Inner
                 {
                     Name = "ss"
                 }
             });
    
            Assert.IsTrue(result);
        }
    }
    
    internal class CustomVisitor : ExpressionVisitor
    {
        private readonly ParameterExpression mParameter;
    
        private int counter = 0;
    
        public CustomVisitor(ParameterExpression parameter)
        {
            mParameter = parameter;
        }
    
        protected override Expression VisitLambda<T>(Expression<T> node)
        {
           return Expression.Lambda(
              Visit(node.Body), 
              node.Parameters.Select(x => (ParameterExpression)Visit(x)).ToArray());
    //or simpler but less generic        
    //return Expression.Lambda(Visit(node.Body), mParameter);
        }
    
        //this method will be called twice first for Name and then for Inner
        protected override Expression VisitMember(MemberExpression node)
        {
            counter++;
            System.Diagnostics.Debug.WriteLine("{0} - {1}", node.ToString(), counter);
    
            if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
                throw new NotImplementedException();
    
            var memberName = node.Member.Name;
            var inner = Visit(node.Expression);
            var otherMember = inner.Type.GetProperty(memberName);
            return Expression.Property(inner, otherMember);
        }
    
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return mParameter;
        }
    }
    

    Note that visit member is called twice and must react accordingly for both calls. Also you need to override the lambda creation as it would fail in parameter replacement.

    PS: Never catch base class Exception its just bad practice and the panic return null on exception is just wrong.