163 votes

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

Question basée sur Exemple MSDN .

Disons que nous avons quelques classes C# avec HelpAttribute dans une application de bureau autonome. Est-il possible d'énumérer toutes les classes avec cet attribut ? Est-il judicieux de reconnaître les classes de cette manière ? L'attribut personnalisé serait utilisé pour lister les options de menu possibles, la sélection d'un élément fera apparaître à l'écran l'instance de cette classe. Le nombre de classes/articles augmentera lentement, mais de cette façon nous pouvons éviter de les énumérer tous ailleurs, je pense.

218voto

Andrew Arnott Points 35346

Oui, absolument. Utilisation de Reflection :

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

7 votes

Je suis d'accord, mais dans ce cas, nous pouvons le faire de manière déclarative comme dans la solution de CasperOne. C'est bien de pouvoir utiliser yield, c'est encore mieux de ne pas avoir à le faire :)

10 votes

J'aime LINQ. Je l'adore, en fait. Mais il est dépendant de .NET 3.5, ce qui n'est pas le cas de yield return. De plus, LINQ finit par se décomposer pour devenir essentiellement la même chose que yield return. Alors qu'avez-vous gagné ? Une syntaxe C# particulière, c'est une préférence.

0 votes

C'est vrai, mais devinez laquelle de ces trois options nécessite le moins de lignes de code et le plus court. Et alloue le moins d'objets (en d'autres termes, fonctionne le plus rapidement) :)

115voto

casperOne Points 49736

Il faudrait alors énumérer toutes les classes de tous les assemblages chargés dans le domaine d'application actuel. Pour ce faire, il faut appeler la fonction GetAssemblies méthode sur le AppDomain pour le domaine d'application actuel.

De là, vous appelez GetExportedTypes (si vous ne voulez que des types publics) ou GetTypes sur chaque Assembly pour obtenir les types qui sont contenus dans l'assemblage.

Ensuite, vous appelez le GetCustomAttributes méthode d'extension sur chaque Type en passant le type de l'attribut que vous souhaitez trouver.

Vous pouvez utiliser LINQ pour vous simplifier la tâche :

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 a été appliqué, ainsi que l'instance du ou des attributs qui lui ont été assignés.

Notez que si vous avez un grand nombre d'assemblages chargés dans votre domaine d'application, cette opération peut être coûteuse. Vous pouvez utiliser LINQ parallèle pour réduire la durée de l'opération (au prix de cycles CPU), comme suit :

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 base spécifique Assembly 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 l'assemblage 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>() };

1 votes

Enumération de tous les types dans tous Les assemblages chargés seraient juste très lents et ne vous apporteraient pas grand-chose. C'est aussi potentiellement un risque de sécurité. Vous pouvez probablement prédire quels assemblages contiendront les types qui vous intéressent. Il suffit d'énumérer les types dans ces assemblages.

0 votes

@Andrew Arnott : C'est vrai, mais c'est ce qui a été demandé. Il est assez facile d'élaguer la requête pour un assemblage particulier. Cela a également l'avantage de vous donner le mappage entre le type et l'attribut.

1 votes

Vous pouvez utiliser le même code sur l'assemblage actuel uniquement avec System.Reflection.Assembly.GetExecutingAssembly().

37voto

Jay Walker Points 2215

Référence d'autres réponses GetCustomAttributes . J'ajoute celui-ci comme exemple d'utilisation IsDefined

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

3 votes

Je pense que c'est la bonne solution pour utiliser la méthode prévue par le cadre.

11voto

CodingWithSpike Points 17720

Comme il a déjà été dit, la réflexion est la meilleure solution. Si vous avez l'intention de l'utiliser fréquemment, je vous 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.

Voici un extrait de mon code qui passe en revue tous les types de tous les assemblages 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.
        }
    }
}

10voto

Il s'agit d'une amélioration des performances en plus de la solution acceptée. L'itération à travers toutes les classes peut être lente car elles sont très nombreuses. 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 du système contiennent des types avec cet attribut. La propriété Assembly.GlobalAssemblyCache est un moyen rapide de vérifier la présence de DLL système. Lorsque j'ai essayé cela sur un programme réel, j'ai constaté que je pouvais ignorer 30 101 types et que je n'avais à vérifier que 1 983 types.

Une autre façon de filtrer est d'utiliser Assembly.ReferencedAssemblies. On peut supposer que si vous voulez des classes avec un attribut spécifique, et que cet attribut est défini dans une assemblée spécifique, alors vous ne vous souciez que de cette assemblée et des autres assemblées qui la référencent. Dans mes tests, cela m'a aidé un peu plus que de vérifier la propriété GlobalAssemblyCache.

J'ai combiné les deux et j'ai obtenu un résultat encore plus rapide. 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)

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