2009-03-03 17 views
119

Domanda basata su MSDN example.Come enumerare tutte le classi con attributo di classe personalizzato?

Diciamo che abbiamo alcune classi C# con HelpAttribute in applicazioni desktop standalone. È possibile enumerare tutte le classi con tale attributo? Ha senso riconoscere le classi in questo modo? L'attributo personalizzato verrebbe utilizzato per elencare le possibili opzioni di menu, selezionando l'elemento verrà visualizzata l'istanza di tale classe. Il numero di classi/elementi crescerà lentamente, ma in questo modo possiamo evitare di elencarli tutti altrove, penso.

+1

Il collegamento di esempio MSDN è un collegamento morto. – MadTigger

risposta

159

Sì, assolutamente. Utilizzando la riflessione:

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

Concordato, ma in questo caso possiamo farlo in modo dichiarativo secondo la soluzione di casperOne. È bello essere in grado di utilizzare la resa, è ancora più bello non dover :) –

+8

Mi piace LINQ. Love it, in realtà. Ma ci vuole una dipendenza da .NET 3.5, che non produce rendimento. Inoltre, LINQ alla fine si riduce sostanzialmente alla stessa cosa del rendimento di rendimento. Quindi cosa hai guadagnato? Una sintassi C# particolare, questa è una preferenza. –

+0

Quindi sta usando rendimento invece di scrivere il proprio enumeratore ... –

80

Beh, si dovrebbe enumerare tutte le classi in tutte le assemblee che vengono caricati nel dominio applicazione corrente. Per farlo, chiami lo GetAssemblies method sull'istanza AppDomain per il dominio dell'app corrente.

Da lì, si chiama GetExportedTypes (se si desidera solo i tipi pubblici) o GetTypes su ogni Assembly per ottenere i tipi contenuti nell'assieme.

Quindi, chiamaresu ciascuna istanza Type, passando il tipo di attributo che si desidera trovare.

È possibile utilizzare LINQ per semplificare questo per voi:

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 si arriva ogni tipo con l'attributo applicato ad esso, insieme con l'istanza dell'attributo (s) assegnato.

Si noti che se è stato caricato un numero elevato di assiemi nel dominio dell'applicazione, tale operazione potrebbe essere costosa. È possibile utilizzare 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>() }; 

filtrandola su uno specifico Assembly è 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 il gruppo ha un gran numero di tipi in esso , quindi è possibile utilizzare Parallel LINQ nuovamente:

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'enumerazione di tutti i tipi in tutti gli assembly caricati sarebbe molto lenta e non ti farà guadagnare molto. È anche potenzialmente un rischio per la sicurezza. Probabilmente puoi prevedere quali gruppi conterranno i tipi a cui sei interessato. Basta elencare i tipi in questi. –

+0

@Andrew Arnott: corretto, ma questo è ciò che è stato chiesto. È abbastanza facile da ridurre la query verso il basso per un particolare assemblaggio. Questo ha anche il vantaggio di darti la mappatura tra il tipo e l'attributo. – casperOne

+0

Grazie per la soluzione, ma non sono ancora veramente abituato a tutto questo LINQ :-) – tomash

8

Come già indicato, la riflessione è la strada da percorrere. Se lo chiamerai spesso, ti consiglio caldamente di memorizzare i risultati nella cache, in quanto la riflessione, in particolare la numerazione di ogni classe, può essere piuttosto lenta.

Si tratta di un frammento del mio codice che attraversa tutti i tipi in tutte le assemblee 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. 
     } 
    } 
} 
18

Altre risposte riferimento GetCustomAttributes.Aggiungendo questo come un esempio di utilizzo IsDefined

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

Credo che sia la soluzione corretta che usa il metodo inteso come framework. –

1

Nel caso del Portable .NET limitations, il codice seguente 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 mediante ciclo stati basata 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

Questo è un miglioramento delle prestazioni in cima alla soluzione accettata. Iterare sebbene tutte le classi possano essere lente perché ce ne sono così tante. A volte puoi filtrare un intero assemblaggio senza guardare a nessuno dei suoi tipi.

Ad esempio, se si sta cercando un attributo dichiarato dall'utente, non ci si aspetta che nessuna DLL di sistema contenga alcun tipo con tale attributo. La proprietà Assembly.GlobalAssemblyCache è un modo rapido per controllare le DLL di sistema. Quando ho provato questo su un programma reale ho scoperto che potevo saltare 30.101 tipi e devo solo controllare 1.983 tipi.

Un altro modo per filtrare è utilizzare Assembly.ReferencedAssemblies. Presumibilmente se vuoi classi con un attributo specifico e quell'attributo è definito in un assembly specifico, allora ti interessa solo quell'assembly e altri assembly che lo fanno riferimento. Nei miei test questo ha aiutato leggermente di più che controllare la proprietà GlobalAssemblyCache.

Ho combinato entrambi e 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) 
Problemi correlati