27

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.

abatishchev
  • 92,232
  • 78
  • 284
  • 421
Dave
  • 18,595
  • 12
  • 59
  • 84
  • possible duplicate of [How to use reflection to call generic Method?](http://stackoverflow.com/questions/232535/how-to-use-reflection-to-call-generic-method) – usr May 30 '14 at 17:03

9 Answers9

21

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).

Noctis
  • 10,865
  • 3
  • 38
  • 74
Neil
  • 6,747
  • 4
  • 40
  • 42
  • 2
    Your alternative approach without looping through Type.GetMethods result got me curious: how well does it perform? Against my expectations the looping is actually [faster](http://www.damirscorner.com/CallAGenericExtensionMethodUsingReflection.aspx). – Damir Arh Jul 24 '11 at 08:43
  • @DamirArh, thanks for testing that out. That is counterintuitive. I still kind of like the static approach, as it gives more of the work to .NET and could take advantage of future optimizations, if they were to occur. However, for performance-critical sections of code, it is good to check. – Neil Oct 17 '11 at 16:19
  • @NeilWhitaker Great work! I'm a newbie and what I want to reflect is "Where" instead of "OrderBy". Could you write a more common/general one? – Lei Yang Dec 16 '13 at 05:59
15

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();
    }
}
Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
  • Interesting, thanks I will need to absorb this SquenceEqual method. – Dave Nov 07 '08 at 15:33
  • 1
    What would one pass as the parameter to parameterTypes when the type of the parameter is generic? – dudeNumber4 Feb 21 '12 at 15:27
  • @dudeNumber4: It's not clear what you mean. I suggest you ask a new question with a concrete example. – Jon Skeet Feb 21 '12 at 15:34
  • 2
    I should be able to clarify: I have MyClass.MyMethod( T gnrc ). If I want to call GetGenericMethod above to get MethodInfo for MyMethod, I would pass typeof(MyClass) as first param, "MyMethod" as second, but what would I pass as the third? – dudeNumber4 Feb 22 '12 at 21:00
  • 2
    @dudeNumber4: Ah, I see. You wouldn't, basically - you have to call `GetMethods` and then filter it yourself. It's a weakness in the reflection API. – Jon Skeet Feb 22 '12 at 21:01
6

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 :(

Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
6

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.

nawfal
  • 62,042
  • 48
  • 302
  • 339
qube
  • 61
  • 1
  • 1
2

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;
}
PaulWh
  • 56
  • 2
  • Important note, this only works on extension methods, otherwise you'll get a NullReferenceException from the col.OrderBy, so you're probably better off using Enumerable.OrderBy to explicitly get the static method you're referring to. If you want to get the an instance method without having a non-null instance you'll need to use the Linq.Expressions method described in the excepted answer. – PaulWh Apr 16 '13 at 00:58
2
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();
Dave
  • 18,595
  • 12
  • 59
  • 84
1

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

http://web.archive.org/web/20100911074123/http://www.nerdington.com/2010/08/calling-generic-method-without-magic.html

Chris Marisic
  • 30,638
  • 21
  • 158
  • 255
Kyle
  • 1,164
  • 8
  • 23
  • This is great, i have been trying all day to figure out a decent way to get a generic method with overloads. I created a somewhat simplified version but your idea helped me a lot getting there ;) – Doggett Nov 04 '10 at 21:12
  • Link has expired. This looks like the [new location](http://www.theoutgoingnerd.com/2010/08/calling-generic-method-without-magic.html). – sh54 Dec 21 '11 at 08:10
0

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
}
Konstantin Isaev
  • 564
  • 7
  • 13
0

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&lt;TSource>(this IEnumerable&lt;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&lt;TSource>(this IEnumerable&lt;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
MBoros
  • 1,040
  • 7
  • 19