Reputation: 575
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
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