Sunday, March 6, 2011

Get a generic method without using GetMethods

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.

From stackoverflow
  • 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 :(

  • 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();
    
  • 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();
        }
    }
    
    Dave : Interesting, thanks I will need to absorb this SquenceEqual method.
  • 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).

    Dave : Ooooh, very wise. :)
  • 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

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

0 comments:

Post a Comment