Domanda

Domanda basata su esempio MSDN .

Supponiamo di avere alcune classi C # con HelpAttribute in un'applicazione desktop autonoma. È possibile enumerare tutte le classi con tale attributo? Ha senso riconoscere le lezioni in questo modo? L'attributo personalizzato verrebbe utilizzato per elencare le possibili opzioni di menu, selezionando l'elemento si aprirà l'istanza dello schermo di tale classe. Il numero di classi / oggetti crescerà lentamente, ma in questo modo possiamo evitare di enumerarli altrove, credo.

È stato utile?

Soluzione

Sì, assolutamente. Utilizzando Reflection:

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

Altri suggerimenti

Bene, dovresti enumerare tutte le classi in tutti gli assembly caricati nel dominio dell'app corrente. Per fare ciò, chiameresti GetAssemblies metodo su AppDomain istanza per l'attuale dominio dell'app.

Da lì, chiameresti GetExportedTypes (se desideri solo tipi pubblici) o GetTypes su ogni < code> Assembly per ottenere i tipi contenuti nell'assembly.

Quindi, chiameresti GetCustomAttributes metodo su ciascun Type , passando il tipo dell'attributo che desideri trovare.

Puoi usare LINQ per semplificare ciò:

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 query sopra mostrerà ogni tipo con l'attributo ad esso applicato, insieme all'istanza degli attributi assegnati ad esso.

Si noti che se si dispone di un numero elevato di assembly caricati nel dominio dell'applicazione, tale operazione potrebbe essere costosa. Puoi usare Parallel LINQ per ridurre il tempo dell'operazione, in questo modo :

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

Filtrarlo su un Assembly specifico è semplice:

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

E se l'assembly contiene un gran numero di tipi, puoi usare di nuovo 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>() };

Altre risposte di riferimento GetCustomAttributes . Aggiungendo questo come esempio dell'uso di IsDefined

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

Come già detto, la riflessione è la strada da percorrere. Se hai intenzione di chiamarlo frequentemente, ti consiglio vivamente di memorizzare nella cache i risultati, in quanto la riflessione, in particolare l'elenco di tutte le classi, può essere piuttosto lenta.

Questo è uno snippet del mio codice che attraversa tutti i tipi in tutti gli assembly caricati:

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

Questo è un miglioramento delle prestazioni oltre alla soluzione accettata. L'iterazione sebbene tutte le classi possano essere lente perché ce ne sono così tante. A volte è possibile filtrare un intero assieme senza guardare alcuno dei suoi tipi.

Ad esempio, se stai cercando un attributo che hai dichiarato tu stesso, non ti aspetti che nessuna delle DLL di sistema contenga alcun tipo con quell'attributo. La proprietà Assembly.GlobalAssemblyCache è un modo rapido per verificare la presenza di DLL di sistema. Quando ho provato questo su un vero programma ho scoperto che potevo saltare 30.101 tipi e dovevo solo controllare 1.983 tipi.

Un altro modo di filtrare è usare Assembly.ReferencedAssemblies. Presumibilmente se vuoi classi con un attributo specifico e quell'attributo è definito in un assieme specifico, allora ti preoccupi solo di quell'assieme e di altri assiemi che lo fanno riferimento. Nei miei test questo ha aiutato un po 'di più rispetto al controllo della proprietà GlobalAssemblyCache.

Ho combinato entrambi e l'ho ottenuto ancora più velocemente. Il codice seguente include entrambi i filtri.

        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)

In caso di Portable .NET limitazioni , il seguente codice dovrebbe funzionare:

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

o per un gran numero di assiemi che utilizzano rendimento restituito basato su stato loop :

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

Possiamo migliorare la risposta di Andrew e convertire il tutto in una query LINQ.

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top