在不使用GetMethods的情况下获取泛型方法

时间:2008-11-06 17:42:20

标签: c# .net generics reflection

我想获得方法System.Linq.Queryable.OrderyBy<T, TKey>(the IQueryable<T> source, Expression<Func<T,TKey>> keySelector)方法,但我一直想出空值。

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.

有没有人有任何见解?我宁愿不循环遍历GetMethods结果。

10 个答案:

答案 0 :(得分:21)

解决了(通过黑客攻击LINQ)!

我在研究同样的问题时看到了你的问题。在找不到好的解决方案之后,我有了查看LINQ表达式树的想法。这就是我想出的:

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);
    }
}
输出:10 3 9

编辑:如果你在编译时不知道类型,那么如何获取方法:

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

请务必将typeof(Program)替换为typeof(WhateverClassYouDeclareTheseMethodsIn)。

答案 1 :(得分:12)

您的解决方案的一种变体,作为扩展方法:

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

答案 2 :(得分:5)

我认为以下扩展方法可以解决问题:

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

调用类似于(只是更改原始代码的最后一行):

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

与其他解决方案有什么不同:结果方法与参数类型完全匹配,而不仅仅是它们的通用基类型。

答案 3 :(得分:4)

我不相信有一种简单的方法可以做到这一点 - 它基本上是反射中缺少的功能,IIRC。你必须遍历这些方法才能找到你想要的方法:(

答案 4 :(得分: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();

答案 5 :(得分:1)

使用lambda表达式可以轻松获得通用方法

    var method = type.GetGenericMethod
            (c => c.Validate((IValidator<object>)this, o, action));

在这里阅读更多相关信息:

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

答案 6 :(得分:1)

如果您在编译时知道这些类型,可以使用更少的代码而不使用Expression类型,或者完全依赖Linq,如下所示:

public static MethodInfo GetOrderByMethod<TElement, TSortKey>() {
    IEnumerable<TElement> col = null;
    return new Func<Func<TElement, TSortKey>, IOrderedEnumerable<TElement>>(col.OrderBy).Method;
}

答案 7 :(得分:0)

我认为它可以像上课一样制作:

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

你甚至可以这样使用它:

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

具有如下属性:

public class SortableAttribute : Attribute 
{
    public SortDirection Direction { get; set; }
    public bool IsDefault { get; set; }
}

和这个枚举:

public enum SortDirection
{
    Ascending,
    Descending
}

答案 8 :(得分:0)

只是另一条评论(它应该是,但由于它太长了,我必须将其作为答案发布)跟随@NeilWhitaker -s答案(这里使用Enumerable.Count),因为我们正在清除字符串:) 为什么不在你的bytype方法中使用表达式树呢? 类似的东西:

    #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

答案 9 :(得分:0)

今天有一个很好的替代方法 Type.MakeGenericMethodParameter。以下代码段检索 Queryable.OrderBy 方法:

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