2008-11-06 14 views
23

Je veux obtenir la méthode System.Linq.Queryable.OrderyBy<T, TKey>(the IQueryable<T> source, Expression<Func<T,TKey>> keySelector) méthode, mais je continue à venir avec des valeurs nulles.Obtenir une méthode générique sans utiliser GetMethods

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. 

Quelqu'un a-t-il un aperçu? Je préférerais ne pas boucler le résultat GetMethods.

+0

double possible (http://stackoverflow.com/questions/232535/how-to-use-reflection-to-call-generic -method) – usr

Répondre

17

Résolu (par le piratage LINQ)

J'ai vu votre question en recherchant le même problème: après avoir trouvé une mauvaise solution, j'ai eu l'idée de regarder l'arbre d'expression de LINQ Voici ce que j'ai trouvé:

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

sortie: 10 3 9

EDIT: Voici comment obtenir la méthode si vous ne connaissez pas le type à la compilation:

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

Assurez-vous de remplacer typeof (programme) avec typeof (WhateverClassYouDeclareTheseMethodsIn).

+1

Ooooh, très sage. :) – Dave

+2

Votre approche alternative sans boucler le résultat de Type.GetMethods m'a fait curieux: comment ça fonctionne? Contre mes attentes, le bouclage est en fait [plus rapide] (http://www.damirscorner.com/CallAGenericExtensionMethodUsingReflection.aspx). –

+0

@DamirArh, merci de tester cela. C'est contre-intuitif. J'aime toujours l'approche statique, car elle donne plus de travail à .NET et pourrait tirer profit des optimisations futures, si elles devaient se produire. Cependant, pour les sections de code critiques pour la performance, il est bon de vérifier. –

3

Je ne crois pas qu'il y ait un moyen facile de le faire - c'est fondamentalement une caractéristique manquante de la réflexion, IIRC. Vous devez boucle à travers les méthodes pour trouver celui que vous voulez :(

+0

Oh mon dieu, je viens d'apprendre ça aujourd'hui .. – nawfal

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

Une variante de votre solution, en tant que méthode d'extension:

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

Intéressant, merci je vais devoir absorber cette méthode SquenceEqual. – Dave

+0

Que passerait-on comme paramètre à parameterTypes lorsque le type du paramètre est générique? – dudeNumber4

+0

@ dudeNumber4: Ce que vous voulez dire n'est pas clair. Je vous suggère de poser une nouvelle question avec un exemple concret. –

1

Utilisation des expressions lambda, vous pouvez obtenir la méthode générique facilement

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

En savoir plus ici:

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

+0

C'est génial, j'ai essayé toute la journée pour trouver une façon décente d'obtenir une méthode générique avec des surcharges. J'ai créé une version un peu simplifiée mais votre idée m'a beaucoup aidé à y arriver;) – Doggett

+0

Le lien a expiré. Cela ressemble à la [nouvelle localisation] (http://www.theoutgoingnerd.com/2010/08/calling-generic-method-without-magic.html). – sh54

4

Je pense que la méthode d'extension suivante serait être une solution au problème:

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

L'appel serait quelque chose comme (juste changer la dernière ligne de votre code d'origine):

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

Ce qui est différent des autres solutions: la méthode résultante correspond aux types de paramètres exactement, non seulement leurs types de base génériques.

0

Je pense que ce soit Mabe fait avec classe comme ceci:

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

Et vous pouvez utiliser ce même comme ceci:

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

avec l'attribut comme ceci:

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

et cette énumération:

public enum SortDirection 
{ 
    Ascending, 
    Descending 
} 
1

Si vous ne connaissez les types au moment de la compilation, vous pouvez le faire avec moins de code sans utiliser le type d'expression, ou en fonction de Linq du tout, comme ceci:

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

Remarque importante, ceci ne fonctionne que sur les méthodes d'extension, sinon vous obtiendrez une exception NullReferenceException de la part de col.OrderBy, donc il vaut probablement mieux utiliser Enumerable.OrderBy pour obtenir explicitement la méthode statique à laquelle vous faites référence. Si vous voulez obtenir une méthode d'instance sans avoir une instance non nulle, vous devrez utiliser la méthode Linq.Expressions décrite dans la réponse exceptée. – PaulWh

0

Juste un autre commentaire (il devrait être, mais depuis trop longtemps, je dois le poster comme une réponse) suite @NeilWhitaker -s réponse (ici en utilisant Enumerable.Count), puisque nous sommes en train d'effacer les chaînes :) pourquoi ne pas utiliser le Arbres d'expression dans votre méthode bytype aussi? Quelque chose comme: [? Comment utiliser la réflexion pour appeler la méthode générique]

#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