如何使用泛型参数的运行时类型调用 IQueryable.OrderBy() 方法?



我需要调用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可以是不同的值类型,如stringint,这些值类型仅在运行时已知。这甚至可以做到吗,如果是,如何做到?

方法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();

最新更新