一尘不染

通用方法的GetMethod

c#

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

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

但获取空值。我究竟做错了什么?


阅读 271

收藏
2020-05-19

共1个答案

一尘不染

但是,先前的答案在某些情况下适用:

  • 它不处理嵌套的泛型类型,例如的参数类型Action<IEnumerable<T>>Action<>例如,它将全部视为匹配项,string.Concat(IEnumerable<string>)并且string.Concat<T>(IEnumerable<T>)如果在字符串类型上"Concat"使用type 搜索,则两者都将匹配IEnumerable<>。真正需要的是递归处理嵌套的泛型类型,同时将所有泛型参数视为彼此匹配,而不管名称如何,而不匹配具体类型。
  • 它返回匹配的第一个方法,而不是像结果一样,如果结果不确定,则抛出异常type.GetMethod()。因此,如果幸运的话,您可能会得到所需的方法,否则可能不会。
  • 有时BindingFlags为了避免歧义,有必要进行指定,例如当派生类方法“隐藏”基类方法时。通常,您想找到基类的方法,但在不知道要寻找的方法在派生类中的特殊情况下,则不需要。或者,您可能知道您正在寻找静态与实例方法,公共与私有方法等,如果不完全匹配,则不想进行匹配。
  • 它并没有解决的另一个主要缺陷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;
}

请注意,IsSimilarType(Type)扩展方法可以公开,并且可能单独使用。我知道,这个名字不是很好-
我们欢迎您提出一个更好的名字,但是可能很难解释它的作用。此外,我还通过检查“
ref”和数组类型(引用被忽略以进行匹配,但数组尺寸必须匹配)来添加了另一个改进。

因此,这就是Microsoft 应该 这样做的方式。其实并不难。

是的,我知道,您可以使用Linq缩短某些逻辑,但是我不是像这样的底层例程对Linq的忠实拥护者,并且除非Linq与原始代码一样容易遵循,否则我不会这样做。
IMO常常不是这样。

如果您喜欢Linq,并且必须这样做,则可以用此替换最里面的部分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()-您必须找到一个真正的(或使用TypeBuilder构建一个)。为了简化此过程,我添加了public class T声明,并添加了逻辑IsSimilarType()来检查它并匹配任何通用参数。如果您需要使用T[],请使用T.MakeArrayType(1)

2020-05-19