82 votes

La réflexion pour Identifier les Méthodes d'Extension

En C# est-il une technique à l'aide de la réflexion pour déterminer si une méthode a été ajouté à une classe comme une extension de la méthode?

Une extension de la méthode telle que celle illustrée ci-dessous est-il possible de déterminer qui Reverse() a été ajouté à la classe string?

public static class StringExtensions
{
    public static string Reverse(this string value)
    {
        char[] cArray = value.ToCharArray();
        Array.Reverse(cArray);
        return new string(cArray);
    }
}

Nous sommes à la recherche d'un mécanisme pour déterminer dans les tests unitaires que la méthode d'extension a été correctement ajouté par le développeur. Une raison de plus pour tenter de ceci est qu'il est possible qu'une telle méthode pourrait être ajouté à la classe réelle par le promoteur et, s'il l'était, le compilateur va choisir cette méthode.

126voto

Jon Skeet Points 692016

Vous avez à regarder dans toutes les assemblées où la méthode d'extension peuvent être définies.

Regardez pour les classes décorées avec des ExtensionAttribute, et puis des méthodes à l'intérieur de cette classe, qui sont également décorées avec des ExtensionAttribute. Ensuite, vérifiez le type de la premier paramètre pour voir si elle correspond au type qui vous intéresse.

Voici un code complet. Il pourrait être plus rigoureux (il n'est pas de vérifier que le type n'est pas imbriquée, ou qu'il y est au moins un paramètre), mais elle devrait vous donner un coup de main.

using System;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;

public static class FirstExtensions
{
    public static void Foo(this string x) {}
    public static void Bar(string x) {} // Not an ext. method
    public static void Baz(this int x) {} // Not on string
}

public static class SecondExtensions
{
    public static void Quux(this string x) {}
}

public class Test
{
    static void Main()
    {
        Assembly thisAssembly = typeof(Test).Assembly;
        foreach (MethodInfo method in GetExtensionMethods(thisAssembly,
            typeof(string)))
        {
            Console.WriteLine(method);
        }
    }

    static IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly,
        Type extendedType)
    {
        var query = from type in assembly.GetTypes()
                    where type.IsSealed && !type.IsGenericType && !type.IsNested
                    from method in type.GetMethods(BindingFlags.Static
                        | BindingFlags.Public | BindingFlags.NonPublic)
                    where method.IsDefined(typeof(ExtensionAttribute), false)
                    where method.GetParameters()[0].ParameterType == extendedType
                    select method;
        return query;
    }
}

13voto

Basé sur John Skeet la réponse que j'ai créé ma propre extension pour le Système.Type-le Type.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace System
{
    public static class TypeExtension
    {
        /// <summary>
        /// This Methode extends the System.Type-type to get all extended methods. It searches hereby in all assemblies which are known by the current AppDomain.
        /// </summary>
        /// <remarks>
        /// Insired by Jon Skeet from his answer on http://stackoverflow.com/questions/299515/c-sharp-reflection-to-identify-extension-methods
        /// </remarks>
        /// <returns>returns MethodInfo[] with the extended Method</returns>

        public static MethodInfo[] GetExtensionMethods(this Type t)
        {
            List<Type> AssTypes = new List<Type>();

            foreach (Assembly item in AppDomain.CurrentDomain.GetAssemblies())
            {
                AssTypes.AddRange(item.GetTypes());
            }

            var query = from type in AssTypes
                where type.IsSealed && !type.IsGenericType && !type.IsNested
                from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
                where method.IsDefined(typeof(ExtensionAttribute), false)
                where method.GetParameters()[0].ParameterType == t
                select method;
            return query.ToArray<MethodInfo>();
        }

        /// <summary>
        /// Extends the System.Type-type to search for a given extended MethodeName.
        /// </summary>
        /// <param name="MethodeName">Name of the Methode</param>
        /// <returns>the found Methode or null</returns>
        public static MethodInfo GetExtensionMethod(this Type t, string MethodeName)
        {
            var mi = from methode in t.GetExtensionMethods()
                where methode.Name == MethodeName
                select methode;
            if (mi.Count<MethodInfo>() <= 0)
                return null;
            else
                return mi.First<MethodInfo>();
        }
    }
}

Elle est de toutes les assemblées de l'actuel domaine d'application et de recherches pour les étendues des méthodes.

Utilisation:

Type t = typeof(Type);
MethodInfo[] extendedMethods = t.GetExtensionMethods();
MethodInfo extendedMethodInfo = t.GetExtensionMethod("GetExtensionMethods");

La prochaine étape serait d'étendre le Système.Type de méthodes, qui renvoie toutes les Méthodes (ou le "normal" avec la élargies)

5voto

drake7707 Points 796

Cela renvoie une liste de toutes les méthodes d'extension définies dans un certain type, y compris les génériques:

public static IEnumerable<KeyValuePair<Type, MethodInfo>> GetExtensionMethodsDefinedInType(this Type t)
{
    if (!t.IsSealed || t.IsGenericType || t.IsNested)
        return Enumerable.Empty<KeyValuePair<Type, MethodInfo>>();

    var methods = t.GetMethods(BindingFlags.Public | BindingFlags.Static)
                   .Where(m => m.IsDefined(typeof(ExtensionAttribute), false));

    List<KeyValuePair<Type, MethodInfo>> pairs = new List<KeyValuePair<Type, MethodInfo>>();
    foreach (var m in methods)
    {
        var parameters = m.GetParameters();
        if (parameters.Length > 0)
        {
            if (parameters[0].ParameterType.IsGenericParameter)
            {
                if (m.ContainsGenericParameters)
                {
                    var genericParameters = m.GetGenericArguments();
                    Type genericParam = genericParameters[parameters[0].ParameterType.GenericParameterPosition];
                    foreach (var constraint in genericParam.GetGenericParameterConstraints())
                        pairs.Add(new KeyValuePair<Type, MethodInfo>(parameters[0].ParameterType, m));
                }
            }
            else
                pairs.Add(new KeyValuePair<Type, MethodInfo>(parameters[0].ParameterType, m));
        }
    }

    return pairs;
}

Il n'y a qu'un seul problème avec ceci: Le Type de retour n'est pas la même chose que vous attendez avec typeof(..), parce que c'est un générique de type de paramètre. Afin de trouver toutes les méthodes d'extension pour un type donné, il vous faudra comparer les GUID de tous les types de base et des interfaces du Type:

public List<MethodInfo> GetExtensionMethodsOf(Type t)
{
    List<MethodInfo> methods = new List<MethodInfo>();
    Type cur = t;
    while (cur != null)
    {

        TypeInfo tInfo;
        if (typeInfo.TryGetValue(cur.GUID, out tInfo))
            methods.AddRange(tInfo.ExtensionMethods);


        foreach (var iface in cur.GetInterfaces())
        {
            if (typeInfo.TryGetValue(iface.GUID, out tInfo))
                methods.AddRange(tInfo.ExtensionMethods);
        }

        cur = cur.BaseType;
    }
    return methods;
}

Pour être complet:

Je garde un dictionnaire de type objets info, que je construis lors de l'itération tous les types de toutes les assemblées:

private Dictionary<Guid, TypeInfo> typeInfo = new Dictionary<Guid, TypeInfo>();

où l' TypeInfo est défini comme:

public class TypeInfo
{
    public TypeInfo()
    {
        ExtensionMethods = new List<MethodInfo>();
    }

    public List<ConstructorInfo> Constructors { get; set; }

    public List<FieldInfo> Fields { get; set; }
    public List<PropertyInfo> Properties { get; set; }
    public List<MethodInfo> Methods { get; set; }

    public List<MethodInfo> ExtensionMethods { get; set; }
}

3voto

James Curran Points 55356

Pour clarifier un point de Jon passe... ", Ajoutant que" une extension de la méthode à une classe ne change pas la classe en aucune façon. C'est juste un peu de filage effectuée par le compilateur C#.

Donc, à l'aide de votre exemple, vous pouvez écrire

string rev = myStr.Reverse();

mais le MSIL écrite à l'assemblée sera exactement comme si vous l'aviez écrit:

string rev = StringExtensions.Reverse(myStr);

Le compilateur est simple vous permettant de vous tromper en pensant que vous êtes à l'appel d'une méthode de Chaîne de caractères.

2voto

David B Points 53123

Une raison de plus pour tenter de ceci est qu'il est possible qu'une telle méthode pourrait être ajouté à la classe réelle par le promoteur et, s'il l'était, le compilateur va choisir cette méthode.

  • Supposons qu'une extension de la méthode void Foo(ce Client someCustomer) est défini.
  • Suppose, également, que le Client est modifié et la méthode void Foo() est ajouté.
  • Ensuite, la nouvelle méthode sur le Client devra couvrir/masquer la méthode d'extension.

Le seul moyen pour appeler la vieille méthode Foo à ce point est:

CustomerExtension.Foo(myCustomer);

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