Last time, I talked a little bit about expression trees and what they are useful for. Expression trees are really interesting to those who want to write LINQ providers and have the ability to reason about the lambda expressions coming into their LINQ provider. However, in general, anyone that want’s to reason about a lambda expression should be interested in expression trees.
Today, I’m going to write particularly to those who are implementing code that reads expression trees. Even if you don’t plan to do that, I hope you follow along because this stuff is interesting to know and understand regardless 🙂
For this post, I wanted to discuss the comparison of strings and their representation in expression trees. Jonathan (our PM) and I were working through a LINQ provider sample, and we thought that this code would be useful to share with you.
In Visual Basic, it’s possible to cause the = operator to do either case sensitive or case insensitive string comparison, via the option compare configuration.
In order to implement these semantics, the Visual Basic compiler emits a call to Microsoft.VisualBasic.CompilerServices.Operators.CompareString in the Visual Basic runtime. This method takes 3 arguments; the two operands, and a flag indicating the comparison type.
When the Visual Basic compiler generates an expression tree for a string equality operator, we decided to encode a call expression to the CompareString method in order to preserve the correct string comparison semantics. This is because by default, the Equal node in the expression tree API assumes case sensitive comparisons. Furthermore, the Equal node doesn’t take into account special VB semantics around string comparisons and the Nothing literal.
This means that if you are writing a visitor over the expression trees generated by the VB compiler, you should expect to see a binary operator for <, <=, =, >=, or >, where the left hand side is a CompareString method and the right hand side is 0. When you see this, you should unpack the arguments in the CompareString method and apply the right string semantics based on the 3rd argument to CompareString.
However, if your provider doesn’t care about string comparison semantics (LINQ to SQL, for example, doesn’t care), then you can use the following code to transform the binary operator for the method call to a binary operator for the method call arguments, ignoring the string comparison semantics.
Call this method in your visitor for binary expressions, and it will convert it appropriately for you. You can then run your visitor over the node returned by this method, and handle string comparison as a binary operator, but you lose the case sensitivity of the comparison.
Friend Shared Function ConvertVBStringCompare(ByVal exp As BinaryExpression) As BinaryExpression If exp.Left.NodeType = ExpressionType.Call Then Dim compareStringCall = CType(exp.Left, MethodCallExpression) If compareStringCall.Method.DeclaringType.FullName = "Microsoft.VisualBasic.CompilerServices.Operators" _ AndAlso compareStringCall.Method.Name = "CompareString" Then Dim arg1 = compareStringCall.Arguments(0) Dim arg2 = compareStringCall.Arguments(1) Select Case exp.NodeType Case ExpressionType.LessThan Return Expression.LessThan(arg1, arg2) Case ExpressionType.LessThanOrEqual Return Expression.GreaterThan(arg1, arg2) Case ExpressionType.GreaterThan Return Expression.GreaterThan(arg1, arg2) Case ExpressionType.GreaterThanOrEqual Return Expression.GreaterThanOrEqual(arg1, arg2) Case Else Return Expression.Equal(arg1, arg2) End Select End If End If Return exp End Function
Here’s an example of how you can use this. This is a really trivial visitor that doesn’t do anything useful, but I just wanted to show how you could use the method defined above.
Sub VisitBinary(ByVal exp As BinaryExpression) exp = ConvertVBStringCompare(exp) Visit(exp.Left) Visit(exp.Right) End Sub
I hope that this helps!