GetMethod用于泛型方法

时间:2010-10-27 17:02:27

标签: c# .net reflection

我正在尝试为Enumerable类型的Where方法检索MethodInfo:

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

但是得到null。我做错了什么?

2 个答案:

答案 0 :(得分:49)

然而,上一个答案适用于某些情况:

  • 它不处理嵌套泛型类型,例如参数类型Action<IEnumerable<T>>。它会将所有Action<>视为匹配项,例如,如果在字符串类型上搜索类型为string.Concat(IEnumerable<string>)的{​​{1}},则string.Concat<T>(IEnumerable<T>)"Concat"都会匹配。真正需要的是递归地处理嵌套泛型类型,同时将所有泛型参数视为彼此匹配而不管名称,而不匹配具体类型。
  • 如果结果不明确,它返回匹配的第一个方法而不是抛出异常,如IEnumerable<>那样。所以,如果你很幸运,你可能会得到你想要的方法,或者你可能没有。
  • 有时需要指定type.GetMethod()以避免歧义,例如派生类方法“隐藏”基类方法时。您通常希望找到基类方法,但不是在您知道要查找的方法在派生类中的特殊情况下。或者,您可能知道您正在寻找静态vs实例方法,公共与私有等,并且如果不准确则不想匹配。
  • 它没有解决BindingFlags的另一个主要错误,因为在查找接口类型的方法时,它也不会搜索基接口的方法。好吧,也许这很挑剔,但这是type.GetMethods()中的另一个主要缺陷,这对我来说是一个问题。
  • 调用GetMethods()效率低下,type.GetMethods()只返回名称匹配的方法,而不是类型中的所有方法。
  • 作为最后的挑选,名称type.GetMember(name, MemberTypes.Method, ...)可能会产生误导,因为您可能会尝试找到一个非泛型方法,由于通用声明,该方法碰巧在参数类型的某处具有类型参数类型。

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

GetMethod()

请注意,/// <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; } 扩展方法可以公开,并且可以单独使用。我知道,名字不是很好 - 欢迎你提出一个更好的名字,但它可能会很长时间来解释它的作用。此外,我通过检查'ref'和数组类型添加了另一项改进(refs被忽略以进行匹配,但数组维度必须匹配)。

所以,这就是微软应该完成它的方式。这真的不是那么难。

是的,我知道,你可以使用Linq来缩短一些逻辑,但我不是像这样的低级别例程中Linq的忠实粉丝,除非Linq很容易遵循原始代码,通常情况并非如此,IMO。

如果你喜欢Linq,那么你必须用这个替换IsSimilarType(Type)的最内部(将8行变为1):

IsSimilarType()

最后一件事:如果您正在寻找具有泛型参数的泛型方法,例如if (thisArguments.Length == arguments.Length) return !thisArguments.Where((t, i) => !t.IsSimilarType(arguments[i])).Any(); ,则必须找到一个类型,它是一个通用参数(Method<T>(T, T[]))传入参数类型(任何人都会这样做,因为'通配符'匹配)。但是,你不能只做IsGenericParameter == true - 你必须找到一个真实的(或使用TypeBuilder构建一个)。为了简化这一过程,我添加了new Type()声明,并向public class T添加了逻辑以检查它并匹配任何通用参数。如果您需要IsSimilarType(),请使用T[]

答案 1 :(得分:25)

不幸的是,.NET Reflection中没有很好地支持泛型。在这种特殊情况下,您需要调用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<,>) });