Search code examples
c#linqexpression-trees

How to build expression tree for Contains<T>


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?


Solution

  • 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;
    }