Reputation: 19667
I want to get the method System.Linq.Queryable.OrderyBy<T, TKey>(the IQueryable<T> source, Expression<Func<T,TKey>> keySelector)
method, but I keep coming up with nulls.
var type = typeof(T);
var propertyInfo = type.GetProperty(group.PropertyName);
var propertyType = propertyInfo.PropertyType;
var sorterType = typeof(Func<,>).MakeGenericType(type, propertyType);
var expressionType = typeof(Expression<>).MakeGenericType(sorterType);
var queryType = typeof(IQueryable<T>);
var orderBy = typeof(System.Linq.Queryable).GetMethod("OrderBy", new[] { queryType, expressionType }); /// is always null.
Does anyone have any insight? I would prefer to not loop through the GetMethods
result.
Upvotes: 27
Views: 15487
Reputation: 2981
Today there is a good alternative with the method Type.MakeGenericMethodParameter
. The following snippet retrieve the Queryable.OrderBy
method:
var TSource = Type.MakeGenericMethodParameter(0);
var TKey = Type.MakeGenericMethodParameter(1);
var orderBy = typeof(Queryable).GetMethod(nameof(Queryable.OrderBy), 2, BindingFlags.Static | BindingFlags.Public, null, CallingConventions.Standard
, new[] { typeof(IQueryable<>).MakeGenericType(TSource), typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(TSource, TKey)) }
, null);
Assert.NotNull(orderBy);
Upvotes: 9
Reputation: 1172
Using lambda expressions you can get the generic method easily
var method = type.GetGenericMethod
(c => c.Validate((IValidator<object>)this, o, action));
Read more about it here:
http://www.nerdington.com/2010/08/calling-generic-method-without-magic.html
Upvotes: 1
Reputation: 7437
Solved (by hacking LINQ)!
I saw your question while researching the same problem. After finding no good solution, I had the idea to look at the LINQ expression tree. Here's what I came up with:
public static MethodInfo GetOrderByMethod<TElement, TSortKey>()
{
Func<TElement, TSortKey> fakeKeySelector = element => default(TSortKey);
Expression<Func<IEnumerable<TElement>, IOrderedEnumerable<TElement>>> lamda
= list => list.OrderBy(fakeKeySelector);
return (lamda.Body as MethodCallExpression).Method;
}
static void Main(string[] args)
{
List<int> ints = new List<int>() { 9, 10, 3 };
MethodInfo mi = GetOrderByMethod<int, string>();
Func<int,string> keySelector = i => i.ToString();
IEnumerable<int> sortedList = mi.Invoke(null, new object[] { ints,
keySelector }
) as IEnumerable<int>;
foreach (int i in sortedList)
{
Console.WriteLine(i);
}
}
output: 10 3 9
EDIT: Here is how to get the method if you don't know the type at compile-time:
public static MethodInfo GetOrderByMethod(Type elementType, Type sortKeyType)
{
MethodInfo mi = typeof(Program).GetMethod("GetOrderByMethod", Type.EmptyTypes);
var getOrderByMethod = mi.MakeGenericMethod(new Type[] { elementType,
sortKeyType });
return getOrderByMethod.Invoke(null, new object[] { }) as MethodInfo;
}
Be sure to replace typeof(Program) with typeof(WhateverClassYouDeclareTheseMethodsIn).
Upvotes: 21
Reputation: 1140
Just another comment (it should be, but since its too long, i have to post it as an answer) following up @NeilWhitaker -s answer (here using Enumerable.Count), since we are in the middle of clearing the strings out :) why not use the Expression trees in your bytype method too? Something like :
#region Count
/// <summary>
/// gets the
/// public static int Count<TSource>(this IEnumerable<TSource> source);
/// methodinfo
/// </summary>
/// <typeparam name="TSource">type of the elements</typeparam>
/// <returns></returns>
public static MethodInfo GetCountMethod<TSource>()
{
Expression<Func<IEnumerable<TSource>, int>> lamda = list => list.Count();
return (lamda.Body as MethodCallExpression).Method;
}
/// <summary>
/// gets the
/// public static int Count<TSource>(this IEnumerable<TSource> source);
/// methodinfo
/// </summary>
/// <param name="elementType">type of the elements</param>
/// <returns></returns>
public static MethodInfo GetCountMethodByType(Type elementType)
{
// to get the method name, we use lambdas too
Expression<Action> methodNamer = () => GetCountMethod<object>();
var gmi = ((MethodCallExpression)methodNamer.Body).Method.GetGenericMethodDefinition();
var mi = gmi.MakeGenericMethod(new Type[] { elementType });
return mi.Invoke(null, new object[] { }) as MethodInfo;
}
#endregion Disctinct
Upvotes: 0
Reputation: 61
I think the following extension method would be a solution to the problem:
public static MethodInfo GetGenericMethod(
this Type type, string name, Type[] generic_type_args, Type[] param_types, bool complain = true)
{
foreach (MethodInfo m in type.GetMethods())
if (m.Name == name)
{
ParameterInfo[] pa = m.GetParameters();
if (pa.Length == param_types.Length)
{
MethodInfo c = m.MakeGenericMethod(generic_type_args);
if (c.GetParameters().Select(p => p.ParameterType).SequenceEqual(param_types))
return c;
}
}
if (complain)
throw new Exception("Could not find a method matching the signature " + type + "." + name +
"<" + String.Join(", ", generic_type_args.AsEnumerable()) + ">" +
"(" + String.Join(", ", param_types.AsEnumerable()) + ").");
return null;
}
The call would be something like (just changing the last line of your original code):
var type = typeof(T);
var propertyInfo = type.GetProperty(group.PropertyName);
var propertyType = propertyInfo.PropertyType;
var sorterType = typeof(Func<,>).MakeGenericType(type, propertyType);
var expressionType = typeof(Expression<>).MakeGenericType(sorterType);
var queryType = typeof(IQueryable<T>);
var orderBy = typeof(Queryable).GetGenericMethod("OrderBy",
new Type[] { type, propertyType },
new[] { queryType, expressionType });
What is different to the other solutions: the resulting method matches the parameter types exactly, not only their generic base types.
Upvotes: 6
Reputation: 56
If you do know the types at compile time, you can do this with less code without using the Expression type, or depending on Linq at all, like so:
public static MethodInfo GetOrderByMethod<TElement, TSortKey>() {
IEnumerable<TElement> col = null;
return new Func<Func<TElement, TSortKey>, IOrderedEnumerable<TElement>>(col.OrderBy).Method;
}
Upvotes: 2
Reputation: 662
I think that it mabe be made with class like so:
public static class SortingUtilities<T, TProperty>
{
public static IOrderedQueryable<T> ApplyOrderBy(IQueryable<T> query, Expression<Func<T, TProperty>> selector)
{
return query.OrderBy(selector);
}
public static IOrderedQueryable<T> ApplyOrderByDescending(IQueryable<T> query, Expression<Func<T, TProperty>> selector)
{
return query.OrderByDescending(selector);
}
public static IQueryable<T> Preload(IQueryable<T> query, Expression<Func<T, TProperty>> selector)
{
return query.Include(selector);
}
}
And you can use this even like so:
public class SortingOption<T> where T: class
{
private MethodInfo ascendingMethod;
private MethodInfo descendingMethod;
private LambdaExpression lambda;
public string Name { get; private set; }
public SortDirection DefaultDirection { get; private set; }
public bool ApplyByDefault { get; private set; }
public SortingOption(PropertyInfo targetProperty, SortableAttribute options)
{
Name = targetProperty.Name;
DefaultDirection = options.Direction;
ApplyByDefault = options.IsDefault;
var utilitiesClass = typeof(SortingUtilities<,>).MakeGenericType(typeof(T), targetProperty.PropertyType);
ascendingMethod = utilitiesClass.GetMethod("ApplyOrderBy", BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase);
descendingMethod = utilitiesClass.GetMethod("ApplyOrderByDescending", BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase);
var param = Expression.Parameter(typeof(T));
var getter = Expression.MakeMemberAccess(param, targetProperty);
lambda = Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(T), targetProperty.PropertyType), getter, param);
}
public IQueryable<T> Apply(IQueryable<T> query, SortDirection? direction = null)
{
var dir = direction.HasValue ? direction.Value : DefaultDirection;
var method = dir == SortDirection.Ascending ? ascendingMethod : descendingMethod;
return (IQueryable<T>)method.Invoke(null, new object[] { query, lambda });
}
}
with attribute like this:
public class SortableAttribute : Attribute
{
public SortDirection Direction { get; set; }
public bool IsDefault { get; set; }
}
and this enum:
public enum SortDirection
{
Ascending,
Descending
}
Upvotes: 0
Reputation: 1499770
A variant of your solution, as an extension method:
public static class TypeExtensions
{
private static readonly Func<MethodInfo, IEnumerable<Type>> ParameterTypeProjection =
method => method.GetParameters()
.Select(p => p.ParameterType.GetGenericTypeDefinition());
public static MethodInfo GetGenericMethod(this Type type, string name, params Type[] parameterTypes)
{
return (from method in type.GetMethods()
where method.Name == name
where parameterTypes.SequenceEqual(ParameterTypeProjection(method))
select method).SingleOrDefault();
}
}
Upvotes: 16
Reputation: 19667
var orderBy =
(from methodInfo in typeof(System.Linq.Queryable).GetMethods()
where methodInfo.Name == "OrderBy"
let parameterInfo = methodInfo.GetParameters()
where parameterInfo.Length == 2
&& parameterInfo[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& parameterInfo[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>)
select
methodInfo
).Single();
Upvotes: 2
Reputation: 1499770
I don't believe there's an easy way of doing this - it's basically a missing feature from reflection, IIRC. You have to loop through the methods to find the one you want :(
Upvotes: 6