GetMethod用于泛型方法

Sib*_*Guy 47 .net c# reflection

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

typeof (Enumerable).GetMethod("Where", new Type[] { 
     typeof(IEnumerable<>), 
     typeof(Func<,>) 
})
Run Code Online (Sandbox Code Playgroud)

但得到null.我究竟做错了什么?

Ken*_*ett 49

然而,之前的答案适用于某些情况:

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

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

所以,这就是微软应该如何做到的.这真的不是那么难.

是的,我知道,你可以使用Linq缩短一些逻辑,但我不是像这样的低级别例程中Linq的忠实粉丝,除非Linq与原始代码一样易于理解, IMO通常不是这种情况.

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

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

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

  • 这有点激烈......所以总结一下这个争论,这里提出的GetMethodExt()确实有助于找到方法,即使它们有通用参数,与现有的GetMethod()不同.但它有一个限制,即它所调用的类型必须匹配作为第二个参数传入的对象的类型 - 如果第二个参数在继承树的下方,它将找不到该方法.公平?仍然看起来像有用的代码(解决我的用例),注意到限制.大家开心吗?Ken,将这个发布到github可能有意义. (2认同)
  • 目前没有找到具有可选参数的方法.一个解决方案(如果可选参数传递为null)是将IsSimilarType测试更改为`if(!parameterInfos [i] .IsOptional &&!parameterInfos [i] .ParameterType.IsSimilarType(parameterTypes [i]))`其他代码更改将需要支持省略可选参数. (2认同)
  • @KenBeckett我发现你太容易把事情看成是针对个人的了。这里没有人对你有任何个人恩怨(**出于任何可以想象的原因!**),我们都对成员们在这里所做的一点点工作表示感谢。也就是说,它并不赋予任何人声称虚假信息的权利。我们中的一些人指出你哪里出了问题。不要质疑我们的动机,请使用编辑功能更正您的答案(如果您发现有问题),或者给我们答复。 (2认同)

Dus*_*ell 25

不幸的是,.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

有了这个,下面的代码将起作用:

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