Sebastian Münster
Sebastian Münster

Reputation: 575

Expression.MethodCallExpression pass MemberExpression as Parameter

I'm trying to create a generic Expression with a method call on

Enumerable.Contains

So basically i want to achive this simple lambda

x => collection.Contains(x.SomeProperty)

My code so far is looking like this:

ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "x");
MemberExpression memberExpression = Expression.Property(parameterExpression, propertyName);

MethodCallExpression methodCall = Expression.Call(
    typeof(Enumerable),
    "Contains",
    new Type[] { typeof(Object) },
    Expression.Constant(new Object[] { 1, 2, 3 }),
    memberExpression
);

But then it dumps

InvalidOperationException: No generic method 'Contains' on type 'System.Linq.Enumerable' is compatible with the supplied type
arguments and arguments. No type arguments should be provided if the
method is non-generic

If i just pass the paramterExpression it works fine, but thats not what i want.

My question is now. Is there a way to pass the Memberexpression to the Contains mehtod call?

Upvotes: 2

Views: 2126

Answers (1)

Jon Hanna
Jon Hanna

Reputation: 113282

It depends on the Type of memberExpression which in turn depends on the type that the property accessed has.

E.g. the following works:

void Main()
{
    string propertyName = "ObjectProperty";
    ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "x");
    MemberExpression memberExpression = Expression.Property(parameterExpression, propertyName);
    MethodCallExpression methodCall = Expression.Call(
        typeof(Enumerable),
        "Contains",
        new Type[] { typeof(object) },
        Expression.Constant(new Object[] { 1, 2, 3 }),
        memberExpression
    );

    Console.WriteLine(Expression.Lambda<Func<T, bool>>(methodCall, parameterExpression).Compile()(new T()));
}

public class T
{
    public object ObjectProperty => 2;
    public int IntProperty => 4;
}

The following does not:

void Main()
{
    string propertyName = "IntProperty";
    ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "x");
    MemberExpression memberExpression = Expression.Property(parameterExpression, propertyName);
    MethodCallExpression methodCall = Expression.Call(
        typeof(Enumerable),
        "Contains",
        new Type[] { typeof(object) },
        Expression.Constant(new Object[] { 1, 2, 3 }),
        memberExpression
    );

    Console.WriteLine(Expression.Lambda<Func<T, bool>>(methodCall, parameterExpression).Compile()(new T()));
}

public class T
{
    public object ObjectProperty => 2;
    public int IntProperty => 4;
}

You could use new Type[] { memberExpression.Type } instead of new Type[] { typeof(object) } to have the code adapt to the type of the property, though you'd also need to have the type of the argument expression (in this case the Constant(new Object[] {...})) match.

Note that you can do implicit casts here only if they are reference types that derive from the type in question (object) so a property that returned string or Uri would be fine (though obviously always false in the check for whether it was contained in an array of 1, 2, 3) but a property that returned int is not as it's a boxing conversion, not a reference up-cast.

Upvotes: 3

Related Questions