Get a generic method without using GetMethods
Asked Answered
T

10

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.

Tanta answered 6/11, 2008 at 17:42 Comment(1)
possible duplicate of How to use reflection to call generic Method?Monosepalous
F
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).

Fauces answered 17/12, 2008 at 1:26 Comment(3)
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.Rorke
@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.Fauces
@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?Ibnsina
E
16

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();
    }
}
Existentialism answered 6/11, 2008 at 19:42 Comment(5)
Interesting, thanks I will need to absorb this SquenceEqual method.Tanta
What would one pass as the parameter to parameterTypes when the type of the parameter is generic?Werbel
@dudeNumber4: It's not clear what you mean. I suggest you ask a new question with a concrete example.Existentialism
I should be able to clarify: I have MyClass.MyMethod<T>( 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?Werbel
@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.Existentialism
C
8

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);
Columbus answered 3/8, 2021 at 20:47 Comment(0)
E
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 :(

Existentialism answered 6/11, 2008 at 17:50 Comment(0)
H
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.

Halftone answered 2/9, 2010 at 15:38 Comment(0)
T
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();
Tanta answered 6/11, 2008 at 19:11 Comment(0)
T
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;
}
Thereupon answered 29/3, 2013 at 1:18 Comment(1)
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.Thereupon
F
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

Faradize answered 10/8, 2010 at 21:39 Comment(2)
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 ;)Humanism
Link has expired. This looks like the new location.Unsearchable
H
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
}
Halfhour answered 24/8, 2012 at 21:53 Comment(0)
A
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
Askwith answered 21/10, 2013 at 16:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.