泛型方法的 GetMethod [英] GetMethod for generic method

查看:13
本文介绍了泛型方法的 GetMethod的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to retrieve MethodInfo for Where method of Enumerable type:

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

但得到空值.我做错了什么?

but get null. What am I doing wrong?

推荐答案

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

That previous answer works for some cases, however:

  • 它不处理嵌套的泛型类型,例如 Action> 的参数类型.它会将所有 Action<> 视为匹配项,例如 string.Concat(IEnumerable)string.Concat(IEnumerable<)如果在字符串类型上搜索 "Concat" 类型为 IEnumerable<> ,则 T>) 都将匹配.真正需要的是递归处理嵌套的泛型类型,同时将所有泛型参数视为相互匹配而不考虑名称而不匹配具体类型.
  • 它返回第一个匹配的方法,而不是在结果不明确时抛出异常,就像 type.GetMethod() 所做的那样.因此,如果幸运的话,您可能会得到想要的方法,也可能不会.
  • 有时需要指定 BindingFlags 以避免歧义,例如当派生类方法隐藏"基类方法时.您通常希望找到基类方法,但不是在您知道要查找的方法在派生类中的特殊情况下.或者,您可能知道您正在寻找静态方法与实例方法、公共方法与私有方法等,并且如果不准确就不想匹配.
  • 它没有解决 type.GetMethods() 的另一个主要错误,因为它在寻找接口类型的方法时也没有搜索方法的基接口.好吧,也许这很挑剔,但 GetMethods() 中的另一个主要缺陷对我来说是个问题.
  • 调用 type.GetMethods() 效率低下,type.GetMember(name, MemberTypes.Method, ...) 将只返回具有匹配名称的方法而不是类型中的所有方法.
  • 作为最后的挑剔,名称 GetGenericMethod() 可能会产生误导,因为您可能试图找到一个非泛型方法,该方法碰巧在参数类型的某处具有类型参数由于通用声明类型.
  • It doesn't handle nested generic types, such as a parameter type of Action<IEnumerable<T>>. It will treat all Action<> as matches, for example, string.Concat(IEnumerable<string>) and string.Concat<T>(IEnumerable<T>) will both match if searching for "Concat" with type IEnumerable<> on the string type. What is really desirable is handling nested generic types recursively, while treating all generic parameters as matching each other regardless of name while NOT matching concrete types.
  • It returns the first method matched rather than throwing an exception if the result is ambiguous, like type.GetMethod() does. So, you might get the method you wanted if you're lucky, or you might not.
  • Sometimes it will be necessary to specify BindingFlags in order to avoid ambiguity, such as when a derived class method 'hides' a base class method. You normally want to find base class methods, but not in a specialized case where you know the method you're looking for is in the derived class. Or, you might know you're looking for a static vs instance method, public vs private, etc. and don't want to match if it's not exact.
  • It doesn't address another major fault with type.GetMethods(), in that it also doesn't search base interfaces for methods when looking for a method on an interface type. OK, maybe that's being picky, but it's another major flaw in GetMethods() that has been a problem for me.
  • Calling type.GetMethods() is inefficient, type.GetMember(name, MemberTypes.Method, ...) will return only methods with a matching name instead of ALL methods in the type.
  • As a final nit-pick, the name GetGenericMethod() could be misleading, since you might be trying to find a non-generic method that happens to have a type parameter somewhere in a parameter type due to a generic declaring type.

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

Here's a version that addresses all of those things, and can be used as a general-purpose replacement for the flawed GetMethod(). Note that two extension methods are provided, one with BindingFlags and one without (for convenience).

/// <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,但数组维度必须匹配).

Note that the IsSimilarType(Type) extension method can be made public and might be useful on its own. I know, the name isn't great - you're welcome to come up with a better one, but it might get really long to explain what it does. Also, I added yet another improvement by checking for 'ref' and array types (refs are ignored for matching, but arrays dimensions must match).

所以,微软应该这样做.真的没那么难.

So, that's how Microsoft should have done it. It's really not that hard.

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

Yeah, I know, you can shorten some of that logic using Linq, but I'm not a huge fan of Linq in low-level routines like this, and also not unless the Linq is about as easy to follow as the original code, which is often NOT the case, IMO.

如果你喜欢 Linq,而且你一定喜欢,你可以用这个替换 IsSimilarType() 最里面的部分(把 8 行变成 1 行):

If you love Linq, and you must, you can replace the inner-most part of IsSimilarType() with this (turns 8 lines into 1):

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

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

One last thing: If you're looking for a generic method with a generic parameter, such as Method<T>(T, T[]), you'll have to find a Type which is a generic parameter (IsGenericParameter == true) to pass in for the parameter type (any one will do, because of the 'wildcard' matching). However, you can't just do new Type() - you have to find a real one (or build one with TypeBuilder). To make this easier, I added the public class T declaration, and added logic to IsSimilarType() to check for it and match any generic parameter. If you need a T[], just use T.MakeArrayType(1).

这篇关于泛型方法的 GetMethod的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆