用于generics方法的GetMethod

我试图检索Enumerabletypes的Where方法的MethodInfo:

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

但是得到空。 我究竟做错了什么?

以前的答案适用于某些情况,但是:

  • 它不处理嵌套genericstypes,如Action<IEnumerable<T>>的参数types。 如果在IEnumerable<>types上search"Concat" ,则会将所有Action<>视为匹配项,例如string.Concat(IEnumerable<string>)string.Concat<T>(IEnumerable<T>)stringtypes。 真正需要的是recursion处理嵌套的genericstypes,而不考虑名称而将所有generics参数视为匹配,而不匹配具体types。
  • 它返回匹配的第一个方法,而不是像例如type.GetMethod()那样在结果不明确时抛出exception。 所以,如果你幸运的话,你可能会得到你想要的方法,否则你可能不会。
  • 有时需要指定BindingFlags以避免歧义,例如派生类方法“隐藏”基类方法时。 您通常需要查找基类方法,但不是在您知道要查找的方法位于派生类中的特定情况下。 或者,你可能知道你正在寻找一个静态和实例方法,公共与私人等,不想匹配,如果不是确切的。
  • 它没有解决type.GetMethods()另一个主要的错误, type.GetMethods()在接口types上查找方法时,它也不search基接口。 好吧,也许这是挑剔的,但这是GetMethods()的另一个主要缺陷,这对我来说是一个问题。
  • 调用type.GetMethods()是效率低下, type.GetMember(name, MemberTypes.Method, ...)将只返回具有匹配名称的方法,而不是types中的所有方法。
  • 作为一个最终的挑剔, GetGenericMethod()这个名字可能会让人误解,因为你可能试图find一个非generics的方法,由于generics的声明types,这个方法碰巧在参数types的某个地方有一个types参数。

这是一个解决所有这些问题的版本,可以用作有缺陷的GetMethod()的通用替代品。 请注意,提供了两种扩展方法,一种使用BindingFlags,一种不使用(为了方便起见)。

 /// <summary> /// Search for a method by name and parameter types. /// Unlike GetMethod(), does 'loose' matching on generic /// parameter types, and searches base interfaces. /// </summary> /// <exception cref="AmbiguousMatchException"/> 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); } /// <summary> /// Search for a method by name, parameter types, and binding flags. /// Unlike GetMethod(), does 'loose' matching on generic /// parameter types, and searches base interfaces. /// </summary> /// <exception cref="AmbiguousMatchException"/> 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!"); } } } } /// <summary> /// Special type used to match any generic parameter type in GetMethodExt(). /// </summary> public class T { } /// <summary> /// 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). /// </summary> 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; } 

请注意, IsSimilarType(Type)扩展方法可以公开并可能对其自己有用。 我知道,这个名字并不是很好,不过我们可以提出一个更好的名字,但是解释它的作用可能会很长。 另外,我通过检查'ref'和数组types(refs在匹配中被忽略,但是数组维度必须匹配)来增加另一个改进。

那么,微软应该这样做呢。 真的不那么难

是的,我知道,你可以用Linq来缩短一些逻辑,但是我并不是像Linq这样的低级程序的粉丝,除非Linq像原始代码一样容易遵循,这往往不是这样的,国际海事组织。

如果你喜欢Linq,而且你必须,你可以用这个replaceIsSimilarType()的最内部分(将8行变成1):

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

最后一件事:如果你正在寻找一个generics参数的generics方法,比如Method<T>(T, T[]) ,你必须find一个Type,它是一个generics参数( IsGenericParameter == true )传入参数types(任何人都会这样做,因为“通配符”匹配)。 然而,你不能只是做new Type() – 你必须find一个真正的(或build立一个TypeBuilder)。 为了使这更容易,我添加了public class T声明,并添加了逻辑到IsSimilarType()来检查它,并匹配任何generics参数。 如果你需要一个T[] ,只需使用T.MakeArrayType(1)

不幸的是,在.NETreflection中,generics没有得到很好的支持。 在这种情况下,您需要调用GetMethods,然后筛选您正在查找的方法的结果集。 像下面的扩展方法应该做的伎俩。

 public static class TypeExtensions { private class SimpleTypeComparer : IEqualityComparer<Type> { 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; } } 

有了这个手,下面的代码将工作:

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