代码之家  ›  专栏  ›  技术社区  ›  SiberianGuy

泛型方法的GetMethod[重复]

  •  47
  • SiberianGuy  · 技术社区  · 15 年前

    我正在尝试检索可枚举类型的Where方法的MethodInfo:

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

    但你得空出来。我做错什么了?

    2 回复  |  直到 11 年前
        1
  •  49
  •   Noctis    11 年前

    然而,前面的答案在某些情况下是有效的:

    • 它不处理嵌套泛型类型,例如 Action<IEnumerable<T>> . 它可以治疗所有人 Action<> 例如,作为匹配项, string.Concat(IEnumerable<string>) string.Concat<T>(IEnumerable<T>) 如果搜索 "Concat" 带类型 IEnumerable<> 在字符串类型上。真正需要的是递归地处理嵌套的泛型类型,同时将所有泛型参数视为彼此匹配,而不考虑名称,同时不匹配具体类型。
    • 如果结果不明确(如 type.GetMethod()
    • 有时需要具体说明 BindingFlags 为了避免歧义,例如当派生类方法“隐藏”基类方法时。您通常希望找到基类方法,但在您知道要查找的方法在派生类中的特殊情况下则不需要。或者,您可能知道您正在寻找一个静态vs实例方法、public vs private方法等,如果不完全匹配,则不希望匹配。
    • 它没有解决 type.GetMethods() ,因为在接口类型上查找方法时,它也不会在基本接口中搜索方法。好吧,也许这太挑剔了,但这是 GetMethods()
    • 打电话 类型.GetMethods() 效率低下, type.GetMember(name, MemberTypes.Method, ...)
    • 作为最后的挑剔,名字 GetGenericMethod() 可能会产生误导,因为您可能会试图找到一个非泛型方法,该方法碰巧由于泛型声明类型而在参数类型中的某个位置具有类型参数。

    这是一个解决所有这些问题的版本,可以作为有缺陷的 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”和数组类型(匹配时忽略ref,但数组维度必须匹配)。

    所以,微软就是这样 已经做到了。其实没那么难。

    是的,我知道,你可以使用Linq来缩短一些逻辑,但是我不是Linq的超级粉丝,在像这样的低级例程中,我也不是Linq的超级粉丝,除非Linq和原来的代码一样容易理解,通常不是这样,IMO。

    如果你爱林肯,而且你必须爱他,你可以替换他内心的大部分 IsSimilarType() 用这个(将8行变成1行):

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

    Method<T>(T, T[]) ,则必须找到一个泛型参数类型( IsGenericParameter == true )传入参数类型(任何一个都可以,因为“通配符”匹配)。但是,你不能 new Type() public class T 声明,并将逻辑添加到 IsSimilarType() 检查它并匹配任何泛型参数。如果你需要 T[] ,使用 T.MakeArrayType(1)

        2
  •  26
  •   Dustin Campbell    15 年前

    不幸的是,.NET反射中不太支持泛型。在这种特殊情况下,需要调用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<,>) });