2008-11-06 11 views
23

Quiero obtener el método System.Linq.Queryable.OrderyBy<T, TKey>(the IQueryable<T> source, Expression<Func<T,TKey>> keySelector) método, pero sigo llegando con nulos.Obtener un método genérico sin utilizar 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. 

¿Alguien tiene alguna idea? Preferiría no recorrer el resultado GetMethods.

+0

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

Respuesta

17

Resuelto (por la piratería LINQ)

Vi su pregunta mientras investigaba el mismo problema. Después de no encontrar una buena solución, tuve la idea de mirar el árbol de expresiones LINQ. Esto es lo que se me ocurrió:

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

de salida: 10 3 9

EDIT: Aquí es cómo conseguir el método si no se conoce el tipo en tiempo de compilación:

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

Asegúrese de reemplazar typeof (Programa) con typeof (WhateverClassYouDeclareTheseMethodsIn).

+1

Ooooh, muy sabio. :) – Dave

+2

Su enfoque alternativo sin pasar por el resultado de Type.GetMethods me dio curiosidad: ¿qué tan bien funciona? Contra mis expectativas, el bucle es en realidad [más rápido] (http://www.damirscorner.com/CallAGenericExtensionMethodUsingReflection.aspx). –

+0

@DamirArh, gracias por probarlo. Eso es contrario a la intuición. Todavía me gusta el enfoque estático, ya que le da más trabajo a .NET y podría aprovechar optimizaciones futuras, si ocurrieran. Sin embargo, para las secciones de código de rendimiento crítico, es bueno verificarlo. –

3

No creo que haya una manera fácil de hacerlo: básicamente es una característica que falta de reflection, IIRC. Hay que colocar a través de los métodos para encontrar el que usted quiere :(

+0

Oh mi DIOS, acabo de enterarme de esto hoy ... – 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

Una variante de la solución, como un método de extensión:

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

Interesante, gracias, tendré que absorber este método SquenceEqual. – Dave

+0

¿Qué pasaría uno como el parámetro para parameterTypes cuando el tipo del parámetro es genérico? – dudeNumber4

+0

@ dudeNumber4: No está claro a qué te refieres. Te sugiero que hagas una nueva pregunta con un ejemplo concreto. –

1

Uso de expresiones lambda se puede obtener el método genérico fácilmente

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

leer mas sobre esto aquí:

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

Esto es genial, he estado intentando todo el día encontrar una forma decente para obtener un método genérico con sobrecargas. Creé una versión algo simplificada, pero tu idea me ayudó mucho a llegar;) – Doggett

+0

El enlace ha expirado. Esto se parece a [la nueva ubicación] (http://www.theoutgoingnerd.com/2010/08/calling-generic-method-without-magic.html). – sh54

4

creo que el siguiente método de extensión haría ser una solución al problema:

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

La llamada sería algo así como (solo cambiando la última línea de su código original):

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

Lo que es diferente a las otras soluciones: el método resultante coincide con los tipos de parámetros exactamente, no solo sus tipos básicos genéricos.

0

Creo que mabe hacerse con la clase de este modo:

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

y se puede usar esto incluso así:

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

con el atributo de esta manera:

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

y esta enumeración:

public enum SortDirection 
{ 
    Ascending, 
    Descending 
} 
1

Si lo hace conocer los tipos en tiempo de compilación, puede hacer esto con menos código sin necesidad de utilizar el tipo de expresiones, o dependiendo de LINQ en absoluto, así:

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

Nota importante, esto solo funciona en métodos de extensión, de lo contrario obtendrá una NullReferenceException de col.OrderBy, por lo que probablemente sea mejor que use Enumerable.OrderBy para obtener explícitamente el método estático al que se refiere. Si desea obtener un método de instancia sin tener una instancia no nula, deberá usar el método Linq.Expressions descrito en la respuesta exceptuada. – PaulWh

0

Sólo otro comentario (lo que debería ser, pero como es demasiado largo, tengo que publicarlo como respuesta) siguiendo la respuesta de @NeilWhitaker -s (aquí usando Enumerable.Count), ya que estamos en el medio de borrar las cadenas :) ¿por qué no usar el ¿Árboles de expresión en tu método bytype también? Algo así como: [? ¿Cómo utilizar la reflexión para llamar al método genérico]

#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 
Cuestiones relacionadas