2008-11-06 8 views
23

Ich möchte die Methode System.Linq.Queryable.OrderyBy<T, TKey>(the IQueryable<T> source, Expression<Func<T,TKey>> keySelector) Methode zu bekommen, aber ich komme mit Nullen.Holen Sie sich eine generische Methode ohne 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. 

Hat jemand einen Einblick? Ich würde lieber das GetMethods Ergebnis nicht durchlaufen.

+0

möglich Duplikat (http://stackoverflow.com/questions/232535/how-to-use-reflection-to-call-generic (Methode) – usr

Antwort

17

Gelöst (von LINQ Hacking)

Ich sah Ihre Frage bei der Untersuchung des gleichen Problems.Nachdem ich keine gute Lösung gefunden hatte, hatte ich die Idee, den LINQ-Ausdrucksbaum zu betrachten.Hier bin ich:

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

Ausgang: 9 10 3

EDIT: Hier ist, wie die Methode zu erhalten, wenn Sie die Art zum Zeitpunkt der Kompilierung nicht wissen:

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

Seien Sie sicher, typeof (Programm) ersetzen typeof (WhateverClassYouDeclareTheseMethodsIn).

+1

Ooooh, sehr weise. :) – Dave

+2

Ihr alternativer Ansatz, ohne das Ergebnis von Type.GetMethods durchzugehen, hat mich neugierig gemacht: Wie gut funktioniert es? Entgegen meinen Erwartungen ist das Looping tatsächlich [schneller] (http://www.damirscorner.com/CALLAGenericExtensionMethodUsingReflection.aspx). –

+0

@DamirArh, vielen Dank für das Testen. Das ist nicht intuitiv. Ich mag den statischen Ansatz immer noch, da er .NET mehr Arbeit zur Verfügung stellt und zukünftige Optimierungen nutzen könnte, falls sie auftreten sollten. Bei performance-kritischen Codeabschnitten ist es jedoch sinnvoll, dies zu überprüfen. –

3

Ich glaube nicht, dass es eine einfache Möglichkeit ist, dies zu tun - es ist im Grunde ein fehlendes Merkmal von Reflexion, IIRC. Sie haben durch die Methoden zur Schleife wollen die, die Sie finden :(

+0

Oh mein GOTT, ich habe das gerade heute gelernt. – 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

Eine Variante Ihrer Lösung als Erweiterung Methode:

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

Interessant, danke, dass ich diese Methode SequenceEqual absorbieren muss. – Dave

+0

Was würde man als Parameter an ParameterTypes übergeben, wenn der Typ des Parameters generisch ist? – dudeNumber4

+0

@ dudeNumber4: Es ist nicht klar, was du meinst. Ich schlage vor, Sie stellen eine neue Frage mit einem konkreten Beispiel. –

1

Mit Lambda-Ausdrücke können Sie die generische Methode erhalten leicht

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

Lesen Sie mehr darüber hier:

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

Das ist großartig, ich habe den ganzen Tag versucht, einen anständigen Weg zu finden, um eine generische Methode mit Überladungen zu bekommen. Ich habe eine etwas vereinfachte Version erstellt, aber deine Idee hat mir sehr geholfen, dorthin zu kommen;) – Doggett

+0

Link ist abgelaufen. Das sieht nach dem [neuen Ort] aus (http://www.theoutgoingnerd.com/2010/08/calling-generic-method-without-magic.html). – sh54

4

Ich denke, die folgende Erweiterungsmethode würde eine Lösung für das Problem sein:

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

Der Anruf wäre so etwas wie (nur die Änderung in der letzten Zeile des ursprünglichen Code) sein:

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

Was ist mit den anderen Lösungen unterscheidet: die resultierende Methode genau die Parametertypen übereinstimmt, nicht nur ihre generischen Basistypen.

0

Ich denke, dass es Mabe wie so mit Klasse gemacht werden:

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

Und Sie können dies auch nutzen, wie 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 }); 
    } 
} 

mit dem Attribut wie folgt aus:

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

und dieses Enum:

public enum SortDirection 
{ 
    Ascending, 
    Descending 
} 
1

Wenn Sie die Typen bei der Kompilierung wissen möchten, können Sie dies ohne mit weniger Code tun, um die Expression Typ verwendet wird, oder in Abhängigkeit von Linq überhaupt, etwa so:

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

Wichtiger Hinweis, dies funktioniert nur mit Erweiterungsmethoden, andernfalls erhalten Sie eine NullReferenceException von der col.OrderBy, so dass Sie besser mit Enumerable.OrderBy arbeiten können, um explizit die statische Methode zu erhalten, auf die Sie sich beziehen. Wenn Sie die Instanzmethode ohne eine Instanz ungleich null abrufen möchten, müssen Sie die in der Ausnahmeantwort beschriebene Methode Linq.Expressions verwenden. – PaulWh

0

Just another Kommentar (es sollte sein, aber da es zu lang ist, muss ich es als Antwort posten) nach @NeilWhitaker -s antwort (hier mit Enumerable.Count), da wir gerade dabei sind, die Strings zu löschen :) warum nicht die Ausdrucksbäume in Ihrer Bytype-Methode auch? So etwas wie: [? Wie Reflektion verwenden generische Methode aufzurufen]

#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