GetMethod para método genérico

Estoy intentando recuperar MethodInfo para el método Where de Enumerable:

typeof (Enumerable).GetMethod("Where", new Type[] { typeof(IEnumerable), typeof(Func) }) 

pero consigue nulo. ¿Qué estoy haciendo mal?

Esa respuesta previa funciona en algunos casos, sin embargo:

  • No maneja tipos generics nesteds, como un tipo de parámetro de Action> . string.Concat(IEnumerable) todas las Action<> como coincidencias, por ejemplo, string.Concat(IEnumerable) y string.Concat(IEnumerable) coincidirán si se busca "Concat" con tipo IEnumerable<> en el tipo de cuerda Lo que es realmente deseable es manejar los tipos generics nesteds de manera recursiva, mientras se tratan todos los parámetros generics para que coincidan entre sí, independientemente del nombre, mientras que NO coinciden con los tipos concretos.
  • Devuelve el primer método coincidente en lugar de arrojar una excepción si el resultado es ambiguo, como type.GetMethod() sí lo hace. Por lo tanto, puede obtener el método que quería si tiene suerte, o no.
  • A veces será necesario especificar BindingFlags para evitar la ambigüedad, como cuando un método de clase derivado ‘oculta’ un método de clase base. Normalmente desea buscar métodos de clase base, pero no en un caso especializado donde sabe que el método que está buscando está en la clase derivada. O bien, es posible que sepa que está buscando un método estático vs instancia, público vs privado, etc. y no desea que coincida si no es exacto.
  • No soluciona otro error importante con type.GetMethods() , ya que tampoco busca interfaces base para métodos cuando busca un método en un tipo de interfaz. OK, tal vez sea exigente, pero es otro gran error en GetMethods() que ha sido un problema para mí.
  • type.GetMethods() es ineficiente, type.GetMember(name, MemberTypes.Method, ...) devolverá solo los métodos con un nombre coincidente en lugar de TODOS los métodos del tipo.
  • Como una última elección, el nombre GetGenericMethod() podría ser engañoso, ya que podría estar tratando de encontrar un método no genérico que tenga un parámetro de tipo en alguna parte de un tipo de parámetro debido a un tipo genérico de statement.

Aquí hay una versión que trata todas esas cosas, y se puede utilizar como un reemplazo de propósito general para el GetMethod() defectuoso GetMethod() . Tenga en cuenta que se proporcionan dos métodos de extensión, uno con BindingFlags y otro sin (por conveniencia).

 ///  /// Search for a method by name and parameter types. /// Unlike GetMethod(), does 'loose' matching on generic /// parameter types, and searches base interfaces. ///  ///  public static MethodInfo GetMethodExt( this Type thisType, string name, params Type[] parameterTypes) { return GetMethodExt(thisType, name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy, parameterTypes); } ///  /// Search for a method by name, parameter types, and binding flags. /// Unlike GetMethod(), does 'loose' matching on generic /// parameter types, and searches base interfaces. ///  ///  public static MethodInfo GetMethodExt( this Type thisType, string name, BindingFlags bindingFlags, params Type[] parameterTypes) { MethodInfo matchingMethod = null; // Check all methods with the specified name, including in base classes GetMethodExt(ref matchingMethod, thisType, name, bindingFlags, parameterTypes); // If we're searching an interface, we have to manually search base interfaces if (matchingMethod == null && thisType.IsInterface) { foreach (Type interfaceType in thisType.GetInterfaces()) GetMethodExt(ref matchingMethod, interfaceType, name, bindingFlags, parameterTypes); } return matchingMethod; } private static void GetMethodExt( ref MethodInfo matchingMethod, Type type, string name, BindingFlags bindingFlags, params Type[] parameterTypes) { // Check all methods with the specified name, including in base classes foreach (MethodInfo methodInfo in type.GetMember(name, MemberTypes.Method, bindingFlags)) { // Check that the parameter counts and types match, // with 'loose' matching on generic parameters ParameterInfo[] parameterInfos = methodInfo.GetParameters(); if (parameterInfos.Length == parameterTypes.Length) { int i = 0; for (; i < parameterInfos.Length; ++i) { if (!parameterInfos[i].ParameterType .IsSimilarType(parameterTypes[i])) break; } if (i == parameterInfos.Length) { if (matchingMethod == null) matchingMethod = methodInfo; else throw new AmbiguousMatchException( "More than one matching method found!"); } } } } ///  /// Special type used to match any generic parameter type in GetMethodExt(). ///  public class T { } ///  /// Determines if the two types are either identical, or are both generic /// parameters or generic types with generic parameters in the same /// locations (generic parameters match any other generic paramter, /// but NOT concrete types). ///  private static bool IsSimilarType(this Type thisType, Type type) { // Ignore any 'ref' types if (thisType.IsByRef) thisType = thisType.GetElementType(); if (type.IsByRef) type = type.GetElementType(); // Handle array types if (thisType.IsArray && type.IsArray) return thisType.GetElementType().IsSimilarType(type.GetElementType()); // If the types are identical, or they're both generic parameters // or the special 'T' type, treat as a match if (thisType == type || ((thisType.IsGenericParameter || thisType == typeof(T)) && (type.IsGenericParameter || type == typeof(T)))) return true; // Handle any generic arguments if (thisType.IsGenericType && type.IsGenericType) { Type[] thisArguments = thisType.GetGenericArguments(); Type[] arguments = type.GetGenericArguments(); if (thisArguments.Length == arguments.Length) { for (int i = 0; i < thisArguments.Length; ++i) { if (!thisArguments[i].IsSimilarType(arguments[i])) return false; } return true; } } return false; } 

Tenga en cuenta que el método de extensión IsSimilarType(Type) puede hacerse público y puede ser útil por sí mismo. Lo sé, el nombre no es muy bueno, puedes inventar uno mejor, pero podría ser realmente largo explicar lo que hace. Además, agregue otra mejora comprobando para 'ref' y tipos de matriz (las referencias se ignoran para la coincidencia, pero las dimensiones de las matrices deben coincidir).

Entonces, así es como Microsoft debería haberlo hecho. Realmente no es tan difícil.

Sí, lo sé, puedes acortar algo de esa lógica usando Linq, pero no soy un gran admirador de Linq en rutinas de bajo nivel como este, y tampoco a menos que el Linq sea tan fácil de seguir como el código original. que a menudo NO es el caso, IMO.

Si te encanta Linq, y debes hacerlo, puedes reemplazar la parte más interna de IsSimilarType() con esto (convierte 8 líneas en 1):

 if (thisArguments.Length == arguments.Length) return !thisArguments.Where((t, i) => !t.IsSimilarType(arguments[i])).Any(); 

Una última cosa: si está buscando un método genérico con un parámetro genérico, como Method(T, T[]) , tendrá que encontrar un Tipo que sea un parámetro genérico ( IsGenericParameter == true ) para pasar por el tipo de parámetro (cualquiera lo hará, debido a la coincidencia 'comodín'). Sin embargo, no puede simplemente hacer un new Type() : debe encontrar uno real (o comstackr uno con TypeBuilder). Para hacerlo más fácil, agregué la statement public class T y agregué lógica a IsSimilarType() para verificarla y hacer coincidir cualquier parámetro genérico. Si necesita un T[] , simplemente use T.MakeArrayType(1) .

Desafortunadamente, los generics no tienen un buen soporte en .NET Reflection. En este caso particular, deberá llamar a GetMethods y luego filtrar el conjunto de resultados para el método que está buscando. Un método de extensión como el siguiente debería ser el truco.

 public static class TypeExtensions { private class SimpleTypeComparer : IEqualityComparer { public bool Equals(Type x, Type y) { return x.Assembly == y.Assembly && x.Namespace == y.Namespace && x.Name == y.Name; } public int GetHashCode(Type obj) { throw new NotImplementedException(); } } public static MethodInfo GetGenericMethod(this Type type, string name, Type[] parameterTypes) { var methods = type.GetMethods(); foreach (var method in methods.Where(m => m.Name == name)) { var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); if (methodParameterTypes.SequenceEqual(parameterTypes, new SimpleTypeComparer())) { return method; } } return null; } } 

Con esto en mano, el siguiente código funcionará:

 typeof(Enumerable).GetGenericMethod("Where", new Type[] { typeof(IEnumerable<>), typeof(Func<,>) });