Search code examples
c#vb.netexpression-treesequality

Force VB.NET to generate the same string comparison expression as C#?


Somewhat similar question here:

Difference between C# and VB.Net string comparison

...but not the same as the one I am asking now.

I am creating a simple expression walker that will convert a lambda into an SQL WHERE clause. I call it like this:

GetEntities<MyEntity>(e => e.MyProperty == MyValue)

C# creates the expression as I would expect which is a BinaryExpression consisting of a MemberExpression on the left and a ConstantExpression on the right which looks like this:

$e.MyProperty == MyValue

VB.NET, however, calls CompareString, to which it passes MyProperty and MyValue as parameters and then checks the return result for 0. When called like this:

GetEntities(Of MyEntity)(Function(e) e.MyProperty = MyValue)

...it generates an expression like this:

.Call Microsoft.VisualBasic.CompilerServices.Operators.CompareString(
    $e.MyProperty, MyValue, False) == 0

This obviously doesn't play too well with my expression walker, so I will have to now walk the method expression to get the value passed into it and blah blah blah.

Is there a way to force VB.NET to generate the same expression tree as C# in all circumstances? I'd hate to have to write a ton of code to account for these significant differences.


Solution

  • As I noted in a comment there is a reason for the difference. The Vb.Net operators does not work in the same way as in C#.

    So the answer to your questions is no, you can't change Vb.Net's operators to work like C#.

    What you can do is to create a transform from Vb expression tree to C# expression tree:

        internal sealed class VbComparisonTransform : ExpressionVisitor
        {
            protected override Expression VisitBinary(BinaryExpression node) {
                if (node == null)
                    throw new ArgumentNullException("node");
    
                if (node.Left.NodeType != ExpressionType.Call)
                    return base.VisitBinary(node);
    
                var callNode = node.Left as MethodCallExpression;
                if (callNode.Method.DeclaringType.FullName != "Microsoft.VisualBasic.CompilerServices.Operators")
                    return base.VisitBinary(node);
                if (callNode.Method.Name != "CompareString")
                    return base.VisitBinary(node);
    
                switch (node.NodeType) {
                    case ExpressionType.LessThan:
                        return Expression.LessThan(callNode.Arguments[0], callNode.Arguments[1]);
                    case ExpressionType.LessThanOrEqual:
                        return Expression.LessThanOrEqual(callNode.Arguments[0], callNode.Arguments[1]);
                    case ExpressionType.Equal:
                        return Expression.Equal(callNode.Arguments[0], callNode.Arguments[1]);
                    case ExpressionType.NotEqual:
                        return Expression.NotEqual(callNode.Arguments[0], callNode.Arguments[1]);
                    case ExpressionType.GreaterThanOrEqual:
                        return Expression.GreaterThanOrEqual(callNode.Arguments[0], callNode.Arguments[1]);
                    case ExpressionType.GreaterThan:
                        return Expression.GreaterThan(callNode.Arguments[0], callNode.Arguments[1]);
                    default:
                        string throwmessage = string.Format(CultureInfo.InvariantCulture, "VB.Net compare expression of type {0} not supported", node.NodeType);
                        throw new NotSupportedException(throwmessage);
                }
            }
        }
    

    and then use it like this:

    public Expression ToCSharpComparisons(Expression expression) {
        if (expression == null)
            throw new ArgumentNullException("expression");
    
        return new VbComparisonTransform().Visit(expression);
    }