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

时间:2018-08-13 03:39:49

标签: c# generics reflection

我需要使用仅在运行时可用的OrderBy<T, TKey>(Func<T, TKey>)值调用TKey方法。在阅读了关于如何将变量用作通用参数的答案之后,我正在尝试以下方法:

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,因此不能与期望参数类型为OrderBy()的{​​{1}}一起使用。但是,Func<T, TKey>可以是不同的值类型,例如TKeystring,这些值类型仅在运行时才知道。甚至可以做到吗?如果可以,怎么办?

1 个答案:

答案 0 :(得分:1)

方法MethodInfo.Invoke()用于通过提供的参数执行方法调用,不能用于生成表达式。要生成可用作Expression方法参数的lambda .OrderBy(),请改用此参数:

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