web-dev-qa-db-fra.com

Comment énumérer toutes les classes avec un attribut de classe personnalisée?

Question basée sur exemple MSDN .

Supposons que nous ayons des classes C # avec HelpAttribute dans une application de bureau autonome. Est-il possible d'énumérer toutes les classes avec un tel attribut? Est-il judicieux de reconnaître les classes de cette façon? Un attribut personnalisé serait utilisé pour lister les options de menu possibles, en sélectionnant l'élément, une instance d'écran de cette classe sera affichée. Le nombre de classes/items augmentera lentement, mais de cette façon nous pouvons éviter de les énumérer tous ailleurs, je pense.

134
tomash

Oui absolument. Utilisation de la réflexion:

static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
    foreach(Type type in Assembly.GetTypes()) {
        if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
            yield return type;
        }
    }
}
180
Andrew Arnott

Eh bien, vous devez énumérer toutes les classes de tous les assemblys chargés dans le domaine d'application actuel. Pour ce faire, vous appelez la méthode GetAssemblies sur le AppDomain instance du domaine d'application actuel.

A partir de là, vous appelez GetExportedTypes (si vous voulez uniquement des types publics) ou GetTypes sur chaque Assembly pour obtenir les types contenus dans Assembly.

Ensuite, vous appelez la méthode GetCustomAttributes sur chaque Type instance, en transmettant le type de l'attribut que vous souhaitez rechercher.

Vous pouvez utiliser LINQ pour simplifier cela pour vous:

var typesWithMyAttribute =
    from a in AppDomain.CurrentDomain.GetAssemblies()
    from t in a.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

La requête ci-dessus vous permettra d'obtenir chaque type auquel votre attribut est appliqué, ainsi que l'instance du ou des attributs qui lui sont affectés.

Notez que si vous avez un grand nombre d'assemblages chargés dans votre domaine d'application, cette opération risque de coûter cher. Vous pouvez utiliser Parallel LINQ pour réduire le temps d’opération, comme ceci:

var typesWithMyAttribute =
    // Note the AsParallel here, this will parallelize everything after.
    from a in AppDomain.CurrentDomain.GetAssemblies().AsParallel()
    from t in a.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

Le filtrer sur une Assembly spécifique est simple:

Assembly assembly = ...;

var typesWithMyAttribute =
    from t in Assembly.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

Et si Assembly contient un grand nombre de types, vous pouvez à nouveau utiliser Parallel LINQ:

Assembly assembly = ...;

var typesWithMyAttribute =
    // Partition on the type list initially.
    from t in Assembly.GetTypes().AsParallel()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };
92
casperOne

Autres réponses référence GetCustomAttributes . Ajouter celui-ci comme exemple d'utilisation de IsDefined

Assembly assembly = ...
var typesWithHelpAttribute = 
        from type in Assembly.GetTypes()
        where type.IsDefined(typeof(HelpAttribute), false)
        select type;
26
Jay Walker

Comme déjà dit, la réflexion est la voie à suivre. Si vous appelez cela fréquemment, je suggère fortement de mettre en cache les résultats, car la réflexion, en particulier l'énumération de toutes les classes, peut être assez lente.

Ceci est un extrait de mon code qui parcourt tous les types de tous les assemblys chargés:

// this is making the assumption that all assemblies we need are already loaded.
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) 
{
    foreach (Type type in Assembly.GetTypes())
    {
        var attribs = type.GetCustomAttributes(typeof(MyCustomAttribute), false);
        if (attribs != null && attribs.Length > 0)
        {
            // add to a cache.
        }
    }
}
9
CodingWithSpike

Il s’agit d’une amélioration des performances par rapport à la solution acceptée. Itérer bien que toutes les classes puissent être lentes car il y en a beaucoup. Parfois, vous pouvez filtrer un assemblage entier sans regarder aucun de ses types. 

Par exemple, si vous recherchez un attribut que vous avez déclaré vous-même, vous ne vous attendez pas à ce que les DLL système contiennent aucun type avec cet attribut. La propriété Assembly.GlobalAssemblyCache est un moyen rapide de vérifier les DLL système. Lorsque j'ai essayé cela sur un programme réel, j'ai découvert que je pouvais sauter 30 101 types et que je n'ai qu'à vérifier 1 983 types.

Une autre façon de filtrer consiste à utiliser Assembly.ReferencedAssemblies. Vraisemblablement, si vous voulez des classes avec un attribut spécifique et que cet attribut est défini dans un assembly spécifique, vous ne vous souciez que de cet assembly et des autres assemblys qui le référencent. Dans mes tests, cela a légèrement aidé plus que la vérification de la propriété GlobalAssemblyCache.

J'ai combiné les deux et je l'ai eu encore plus vite. Le code ci-dessous inclut les deux filtres.

        string definedIn = typeof(XmlDecoderAttribute).Assembly.GetName().Name;
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            // Note that we have to call GetName().Name.  Just GetName() will not work.  The following
            // if statement never ran when I tried to compare the results of GetName().
            if ((!Assembly.GlobalAssemblyCache) && ((Assembly.GetName().Name == definedIn) || Assembly.GetReferencedAssemblies().Any(a => a.Name == definedIn)))
                foreach (Type type in Assembly.GetTypes())
                    if (type.GetCustomAttributes(typeof(XmlDecoderAttribute), true).Length > 0)
6
Trade-Ideas Philip

Dans le cas où Portable .NET limitations , le code suivant devrait fonctionner:

    public static IEnumerable<TypeInfo> GetAtributedTypes( Assembly[] assemblies, 
                                                           Type attributeType )
    {
        var typesAttributed =
            from Assembly in assemblies
            from type in Assembly.DefinedTypes
            where type.IsDefined(attributeType, false)
            select type;
        return typesAttributed;
    }

ou pour un grand nombre d'assemblages à l'aide de yield return basé sur l'état de boucle:

    public static IEnumerable<TypeInfo> GetAtributedTypes( Assembly[] assemblies, 
                                                           Type attributeType )
    {
        foreach (var Assembly in assemblies)
        {
            foreach (var typeInfo in Assembly.DefinedTypes)
            {
                if (typeInfo.IsDefined(attributeType, false))
                {
                    yield return typeInfo;
                }
            }
        }
    }
2
Lorenz Lo Sauer

Nous pouvons améliorer la réponse d'Andrew et convertir le tout en une seule requête LINQ.

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return Assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
0
Tachyon