48 votes

GetMethod pour la méthode générique

J'essaie de récupérer MethodInfo pour la méthode Where de type Enumerable:

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

mais deviens nul. Qu'est-ce que je fais mal?

49voto

Ken Beckett Points 577

Que la réponse précédente qui fonctionne dans certains cas, cependant:

  • Il ne gère pas les types génériques imbriqués, comme un type de paramètre de Action<IEnumerable<T>>. Il permettra de traiter toutes Action<> , allumettes, par exemple, string.Concat(IEnumerable<string>) et string.Concat<T>(IEnumerable<T>) sera à la fois du match, si vous cherchez une "Concat" type IEnumerable<> sur le type string. Ce qui est vraiment souhaitable est de la manipulation de types génériques imbriqués de manière récursive, en les traitant de tous les paramètres génériques en respectant les uns les autres indépendamment de nom, tout en ne correspondant PAS à des types concrets.
  • Il retourne la première méthode correspondait plutôt que de lancer une exception si le résultat est ambigu, comme type.GetMethod() n'. Donc, vous pourriez avoir de la méthode que vous avez voulu, si vous avez de la chance, ou vous ne pourriez pas.
  • Parfois, il sera nécessaire de spécifier BindingFlags afin d'éviter toute ambiguïté, comme une méthode de la classe dérivée "se cache" une méthode de classe de base. Normalement, vous voulez trouver des méthodes de classe de base, mais pas dans un cas spécial où vous savez que la méthode que vous cherchez se trouve dans la classe dérivée. Ou bien, vous pouvez savoir que vous êtes à la recherche d'un statique vs méthode d'instance, public vs privé, etc. et vous ne voulez pas de match si ce n'est pas exact.
  • Il ne traite pas de l'autre défaut majeur avec type.GetMethods(), en qui elle aussi n'a pas de base de recherche d'interfaces pour les méthodes lors de la recherche d'une méthode sur un type d'interface. OK, c'est peut-être pointilleux, mais c'est un autre défaut majeur, en GetMethods() qui a été un problème pour moi.
  • Appelant type.GetMethods() est inefficace, type.GetMember(name, MemberTypes.Method, ...) sera de retour que les méthodes avec un nom correspondant au lieu de TOUTES les méthodes dans le type.
  • Comme un dernier nit-pick, le nom de l' GetGenericMethod() pourrait être trompeur, car vous pourriez être en essayant de trouver une non-méthode générique qui arrive à avoir un type de paramètre quelque part dans un type de paramètre en raison d'un générique de déclarer le type.

Voici une version qui répond à toutes ces choses, et peut être utilisé comme un remplacement pour l'imparfait GetMethod(). Notez que les deux méthodes d'extension sont prévus, l'un avec BindingFlags et l'autre sans (pour plus de commodité).

/// <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;
}

Notez que l' IsSimilarType(Type) méthode d'extension peut être rendu public et peut être utile sur son propre. Je sais, le nom n'est pas grand - vous êtes les bienvenus à venir avec un meilleur, mais il peut être vraiment long à expliquer ce qu'il fait. Aussi, j'ai ajouté encore une autre amélioration en vérifiant 'ref' et les types d'array (refs sont ignorés pour la correspondance, mais les matrices de dimensions doivent correspondre).

Donc, c'est comment Microsoft devrait l'avoir fait. Ce n'est pas vraiment dur.

Ouais, je sais, vous pouvez réduire une partie de cette logique à l'aide de Linq, mais je ne suis pas un grand fan de Linq dans les routines de bas niveau comme ça, et aussi pas moins que le Linq est à peu près aussi facile à suivre que le code d'origine, qui n'est PAS souvent le cas, de l'OMI.

Si vous aimez Linq, et vous devez, vous pouvez remplacer l'intérieur de la plus grande partie de l' IsSimilarType() avec ce (tours 8 lignes en 1):

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

Une dernière chose: Si vous êtes à la recherche d'une méthode générique avec un paramètre générique, comme Method<T>(T, T[]), vous aurez à trouver un Type qui est un paramètre générique (IsGenericParameter == true) pour passer pour le type de paramètre (n'importe lequel, du fait de la 'générique' matching). Cependant, vous ne pouvez pas tout new Type() - vous avez de trouver un réel (ou en construire un avec TypeBuilder). Pour rendre cela plus facile, j'ai ajouté de l' public class T déclaration, et a ajouté une logique d' IsSimilarType() les vérifier et correspond à aucun paramètre générique. Si vous avez besoin d'un T[], il suffit d'utiliser T.MakeArrayType(1).

26voto

Dustin Campbell Points 6323

Malheureusement, les génériques ne sont pas bien pris en charge dans .NET Reflection. Dans ce cas particulier, vous devrez appeler GetMethods, puis filtrer le jeu de résultats correspondant à la méthode recherchée. Une méthode d'extension comme celle-ci devrait faire l'affaire.

 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;
    }
}
 

Avec ceci en main le code suivant fonctionnera:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X