How to build expression tree for Contains

c# expression-trees linq

Question

I'm following this SO answer to convert lambda expressions to partial SQL syntax.

However, I have problems parsing the expression for Contains. I added a method:

private bool ParseContainsExpression(MethodCallExpression expression)
{
    MemberExpression member = (MemberExpression)expression.Arguments[0];

    var methodInfo = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });

    //TODO check if list contains value

    return false;
}

As I'm totally new to expressions, I don't know from where get the property name and value, and the list that contains the values I want to check against. Where are these properties and values stored within the expression?

Accepted Answer

Basically you need to process the Method, Object(expression that represents the instance for instance method calls or null for static method calls) and Arguments(a collection of expressions that represent arguments of the called method) properties of the MethodCallExpression class.

Specifically for Contains, you need to avoid (or process differently if needed) the string.Contains method, and also handle static methods like Enumerable.Contains as well as instance methods like ICollection<T>.Contains, List<T>.Contains etc. In order to get the list values (when possible), you have to find some sort of a constant expression.

Here is a sample:

private bool ParseContainsExpression(MethodCallExpression expression)
{
    // The method must be called Contains and must return bool
    if (expression.Method.Name != "Contains" || expression.Method.ReturnType != typeof(bool)) return false;
    var list = expression.Object;
    Expression operand;
    if (list == null)
    {
        // Static method
        // Must be Enumerable.Contains(source, item)
        if (expression.Method.DeclaringType != typeof(Enumerable) || expression.Arguments.Count != 2) return false;
        list = expression.Arguments[0];
        operand = expression.Arguments[1];
    }
    else
    {
        // Instance method
        // Exclude string.Contains
        if (list.Type == typeof(string)) return false;
        // Must have a single argument
        if (expression.Arguments.Count != 1) return false;
        operand = expression.Arguments[0];
        // The list must be IEnumerable<operand.Type>
        if (!typeof(IEnumerable<>).MakeGenericType(operand.Type).IsAssignableFrom(list.Type)) return false;
    }
    // Try getting the list items
    object listValue;
    if (list.NodeType == ExpressionType.Constant)
        // from constant value
        listValue = ((ConstantExpression)list).Value;
    else
    {
        // from constant value property/field
        var listMember = list as MemberExpression;
        if (listMember == null) return false;
        var listOwner = listMember.Expression as ConstantExpression;
        if (listOwner == null) return false;
        var listProperty = listMember.Member as PropertyInfo;
        listValue = listProperty != null ? listProperty.GetValue(listOwner.Value) : ((FieldInfo)listMember.Member).GetValue(listOwner.Value);
    }
    var listItems = listValue as System.Collections.IEnumerable;
    if (listItems == null) return false;

    // Do whatever you like with listItems

    return true;
}

Popular Answer

Your implementation is quite different from the example answer. You really need to inherit from ExpressionVisitor so that you can properly parse the tree.

Let's take this expression for an example:

var myList = new List<string> { "A" };
Expression<Func<string, bool>> a = (s) => myList.Contains(s);
ParseContainsExpression(a.Body as MethodCallExpression);

private bool ParseContainsExpression(MethodCallExpression expression)
{
    expression.Object; //myList
    expression.Arguments[0]; //s    
    return false;
}

Note however, these are still Expressions, they are not actual values yet. You need to invoke the expression to get the values.

However, in our case - myList is actually a ConstantExpression. So we can do this:

((expression.Object as MemberExpression).Expression as ConstantExpression).Value; //myList

Which returns us the original list. Note that it's a field access, because the expression is compiled into a closure, which puts myList as a field in the closure class. As you can see, we have to make a lot of assumptions as to the type of Expression we're handling (that it's a field access and then a constant expression). You really need a fully-fledged visitor implementation to do this properly (which the linked answer describes)



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why