我需要调用OrderBy<T, TKey>(Func<T, TKey>)
方法,其值为TKey
仅在运行时可用。在阅读了有关如何将变量用作通用参数的 SO 答案后,我正在尝试以下方法:
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);
问题是,genericMethodInfo.Invoke()
返回object
,因此不能与需要类型Func<T, TKey>
参数的OrderBy()
一起使用。但是,TKey
可以是不同的值类型,如string
int
,这些值类型仅在运行时已知。这甚至可以做到吗,如果是,如何做到?
方法MethodInfo.Invoke()
用于使用提供的参数执行方法调用,不能用于生成表达式。要生成可用作.OrderBy()
方法参数的 lambdaExpression
,请改用以下内容:
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);
你可能会遇到另一个问题:你不能简单地调用myQueryable.OrderBy(lambda)
,因为这不允许编译者推断它的泛型参数,你不能提供这些泛型参数,因为TKey
在编译时是未知的。因此,您需要进行另一个反射,以实际调用.OrderBy()
方法:
// 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();