Search code examples
linqexpressionvisitor

Use ExpressionVisitor to change 'obj == value' to 'obj.Equals(value)'


I'm trying to compare an object with a random value, which could be an ID, and ObjectKey or even with the same object. In short, I want to compare an object with anything, not just the same type.

To do this, I overrode the Equals() and GetHashCode() for the object, and it is working as expected. But I noticed Linq will not call these methods when I search via 'obj == value'.

If I change the queries to 'obj.Equals(value)', the Equals() method is called as it should. But it's not what I need.

Further, I've tried to overload '==' and '!=' operators, but as I'm searching via interfaces, these overloads are not being called.

At the end, I can't just change all my queries by hand, because someone may use the '==' anywhere in the future, breaking the code.

So I come to ExpressionVisitor. I noticed I can rewrite expressions for my Linq queries, but I'm kinda clueless. I've tried some examples I found, but I got some sort of errors.

Finally, this is what I need via ExpressionVisitor:

replace this: var objects = ctx.Where(obj => obj == value);

to this: var objects = ctx.Where(obj => obj.Equals(value));

Is it possible?


Solution

  • Yay. Found it:

    class Program
    {
        static void Main(string[] args)
        {
            //the sample:
            Expression<Func<string, bool>> expr = name => name == "AA" || name.Length > 0 || name != "b";
            Console.WriteLine(expr);
            EqualsModifier treeModifier = new EqualsModifier();
            Expression modifiedExpr = treeModifier.Modify((Expression)expr);
            Console.WriteLine(modifiedExpr);
            Console.ReadLine();
        }
    }
    //the ExpressionVisitor
    public class EqualsModifier : ExpressionVisitor
    {
        public Expression Modify(Expression expression)
        {
            return Visit(expression);
        }
        protected override Expression VisitBinary(BinaryExpression b)
        {
            if (b.NodeType == ExpressionType.Equal)
            {
                Expression left = this.Visit(b.Left);
                Expression right = this.Visit(b.Right);
                MethodInfo equalsMethod = typeof(string).GetMethod("Equals", new[] { typeof(string) });
                return Expression.Call(left, equalsMethod, right);
            }
            else if (b.NodeType == ExpressionType.NotEqual)
            {
                Expression left = this.Visit(b.Left);
                Expression right = this.Visit(b.Right);
                MethodInfo equalsMethod = typeof(string).GetMethod("Equals", new[] { typeof(string) });
                return Expression.Not(Expression.Call(left, equalsMethod, right));
            }
            return base.VisitBinary(b);
        }
    }
    

    These all output to:

    Original: name => (((name == "AA") OrElse (name.Length < 0)) OrElse (name != "b"))

    Converted: name => ((name.Equals("AA") OrElse (name.Length < 0)) OrElse Not(name.Equals("b")))