views:

2017

answers:

6

I want to get the method System.Linq.Queryable.OrderyBy(the IQueryable source, Expression> keySelector) mehthod, 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.

A: 

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  A: 
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
+2  A: 

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
Interesting, thanks I will need to absorb this SquenceEqual method.
Dave
+1  A: 

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

Neil Whitaker
Ooooh, very wise. :)
Dave
+1  A: 

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

Kyle
A: 

Hi there,

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

var orderBy = typeof(System.Linq.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.

qube