Reputation: 139
I need to call the OrderBy<T, TKey>(Func<T, TKey>)
method with a value for TKey
only available at runtime. After reading answers on SO on how to use a variable as a generic parameter, I'm trying the following approach:
string key = "MyProperty";
Type keyType = typeof(T).GetProperty(key).PropertyType;
MethodInfo methodInfo = typeof(MyClass)
.GetMethod(
"MyGenericStaticMethod"),
BindingFlags.NonPublic | BindingFlags.Static);
// T is known at compile time.
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(new[] { typeof(T), keyType});
var expression = genericMethodInfo.Invoke(null, new object[] { params });
myQueryable.OrderBy(expression);
The problem is, genericMethodInfo.Invoke()
returns object
and hence cannot be used with OrderBy()
which expects an argument of type Func<T, TKey>
. However, TKey
can be different value types like string
,int
that are only known at runtime. Can this even be done and if so, how?
Upvotes: 1
Views: 257
Reputation: 6217
Method MethodInfo.Invoke()
is used to perform method call with provided parameters, and cannot be used to generate expression. To generate lambda Expression
that can be used as an argument to .OrderBy()
method, use this instead:
string key = "MyProperty";
Type keyType = typeof(T).GetProperty(key).PropertyType;
MethodInfo methodInfo = typeof(MyClass)
.GetMethod(
"MyGenericStaticMethod",
BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(new[] { typeof(T), keyType });
//this represents parameter of keySelector expression used in OrderBy method
var parameterExpression = Expression.Parameter(typeof(T));
// Expression representing call to MyGenericStaticMethod
var expression = Expression.Call(genericMethodInfo, parameterExpression);
// To use it as an argument of OrderBy method, we must convert expression to lambda
var lambda = Expression.Lambda(expression, parameterExpression);
You will probably encounter another problem: You cannot simply call myQueryable.OrderBy(lambda)
, because this doesn't allow compiller to infer it's generic arguments and you cannot provide these generic arguments, because TKey
is not known at compile time. So you will need to do another reflection, to actually call an .OrderBy()
method:
// OrderBy method has generic parameters and several overloads. It is thus
// difficult to get it's MethodInfo just by typeof(Queryable).GetMethod().
// Although it may seem weird, but it is easier to get it's MethodInfo from
// some arbitrary expression. Generic arguments "<object, object>" does not
// matter for now, we will replace them later
Expression<Func<IQueryable<object>, IQueryable<object>>> orderByExpression =
x => x.OrderBy<object, object>((o) => null);
// Replace generic parameters of OrderBy method with actual generic arguments
var orderByMethodInfo = (orderByExpression.Body as MethodCallExpression)
.Method
.GetGenericMethodDefinition()
.MakeGenericMethod(new[] { typeof(T), keyType });
// Now we are finally ready to call OrderBy method
var orderedResultQuery = orderByMethodInfo.Invoke(
null,
new Object[] { myQueryable, lambda })
as IQueryable<T>;
// Just for testing purpose, let's materialize result to list
var orderedResult = orderedResultQuery.ToList();
Upvotes: 1