Question

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 qui affichera à l’écran une instance de cette classe. Le nombre de classes / d'objets augmentera lentement, mais je pense que de cette façon, nous pourrons éviter de les énumérer tous ailleurs.

Était-ce utile?

La solution

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

Autres conseils

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 GetAssemblies méthode sur la AppDomain instance du domaine d'application actuel.

À partir de là, vous appelez GetExportedTypes (si vous souhaitez uniquement des types publics) ou GetTypes sur chaque < code> Assembly pour obtenir les types contenus dans l'assembly.

Ensuite, vous appelez le GetCustomAttributes méthode sur chaque Type

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 LINQ parallèle pour réduire le temps de l'opération, par exemple. :

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 filtrage sur un assemblage 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'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>() };

Les autres réponses font référence à GetCustomAttributes . Ajout de 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;

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

Il s'agit d'une amélioration des performances s'ajoutant à 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 examiner 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 assemblage spécifique, vous ne vous préoccupez que de cet assemblage et des autres assemblys qui le référencent. Lors de mes tests, cela a légèrement contribué à 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)

En cas de 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'assemblys à l'aide de return 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;
                }
            }
        }
    }

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);
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top