2009-03-03 7 views
119

Question basé sur MSDN example. 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-ce logique de reconnaître les classes de cette façon? L'attribut personnalisé serait utilisé pour lister les options de menu possibles, la sélection de l'élément entraînera l'affichage à l'écran de cette classe. Le nombre de classes/items augmentera lentement, mais de cette façon nous pouvons éviter de les énumérer tous ailleurs, je pense.Comment énumérer toutes les classes avec l'attribut de classe personnalisée?

+1

Un exemple de lien MSDN est un lien mort. – MadTigger

Répondre

159

Oui, absolument. En utilisant 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; 
     } 
    } 
} 
+5

D'accord, mais dans ce cas, nous pouvons le faire de façon déclarative selon la solution de casperOne. C'est agréable de pouvoir utiliser le rendement, c'est encore mieux de ne pas avoir à le faire :) –

+8

J'aime LINQ. J'adore, en fait. Mais il faut une dépendance sur .NET 3.5, ce qui n'est pas le cas du rendement. En outre, LINQ se décompose finalement essentiellement en la même chose que le rendement du rendement. Alors qu'avez-vous gagné? Une syntaxe C# particulière, c'est une préférence. –

+0

Donc utilise le rendement au lieu d'écrire votre propre énumérateur ... –

80

Eh bien, il vous faudrait énumérer toutes les classes dans toutes les assemblées qui sont chargés dans le domaine de l'application actuelle. Pour ce faire, appelez le GetAssemblies method sur l'instance AppDomain pour le domaine d'application actuel. À partir de là, vous appelez GetExportedTypes (si vous voulez uniquement les types publics) ou GetTypes sur chaque Assembly pour obtenir les types qui sont contenus dans l'assembly.

Ensuite, vous appelez le GetCustomAttributes method sur chaque instance Type, en passant le type de l'attribut que vous souhaitez trouver.

Vous pouvez utiliser LINQ pour simplifier 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 obtiendrez chaque type avec votre attribut appliqué, ainsi que l'instance de l'attribut (s) qui lui est attribué.

Notez que si vous avez un grand nombre d'assemblys chargés dans votre domaine d'application, cette opération peut être coûteuse. Vous pouvez utiliser Parallel LINQ pour réduire le temps de l'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>() }; 

Filtrage sur un 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 l'assemblée a un grand nombre de types dans ce , alors vous pouvez utiliser à nouveau 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

L'énumération de tous les types dans les assemblys _all_ chargés serait très lente et ne vous rapportera pas grand-chose. C'est aussi potentiellement un risque pour la sécurité. Vous pouvez probablement prédire quels assemblages contiendront les types qui vous intéressent. Il vous suffit d'énumérer les types dans ceux-ci. –

+0

@Andrew Arnott: Oui, 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 la correspondance entre le type et l'attribut. – casperOne

+0

Merci pour la solution, mais je ne suis pas vraiment habitué à tout ce truc LINQ :-) – tomash

8

Comme déjà indiqué, 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 à travers chaque classe, peut être assez lente.

C'est un extrait de mon code qui passe par tous les types dans toutes les assemblées chargées:

// 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. 
     } 
    } 
} 
18

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

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

Je crois que c'est la bonne solution qui utilise la méthode prévue par le cadre. –

1

En cas de Portable .NET limitations, le code 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 en utilisant l'état de la boucle à base yield return:

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

Il s'agit d'une amélioration des performances par rapport à la solution acceptée. Itérer si toutes les classes peuvent être lentes parce qu'il y en a tellement. 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 des types avec cet attribut. La propriété Assembly.GlobalAssemblyCache est un moyen rapide de vérifier les DLL système. Quand j'ai essayé ceci sur un vrai programme j'ai trouvé que je pourrais sauter 30.101 types et je dois seulement vérifier 1.983 types.

Une autre façon de filtrer est d'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, alors vous vous souciez uniquement de cet assembly et des autres assemblys qui le référencent. Dans mes tests, cela a aidé légèrement plus que la vérification de la propriété GlobalAssemblyCache. J'ai combiné les deux et je l'ai obtenu encore plus rapidement. 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) 
Questions connexes