2009-03-03 9 views
119

Frage basiert auf MSDN example.Wie alle Klassen mit benutzerdefinierten Klassenattribut aufzählen?

Nehmen wir an, wir haben einige C# -Klassen mit HelpAttribute in einer eigenständigen Desktop-Anwendung. Ist es möglich, alle Klassen mit einem solchen Attribut aufzuzählen? Macht es Sinn, Klassen auf diese Weise zu erkennen? Ein benutzerdefiniertes Attribut würde verwendet werden, um mögliche Menüoptionen aufzulisten, wobei das Auswählen eines Elements zu einer Bildschirminstanz einer solchen Klasse führt. Die Anzahl der Klassen/Elemente wird langsam wachsen, aber auf diese Weise können wir vermeiden, sie alle anderswo aufzuzählen, denke ich.

+1

MSDN Beispiel Link ist eine tote Verbindung. – MadTigger

Antwort

159

Ja, absolut. Verwenden von Reflection:

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

Einverstanden, aber in diesem Fall können wir es laut CasperOne-Lösung deklarativ tun. Es ist schön, in der Lage zu sein, Ausbeute zu verwenden, es ist sogar noch netter, nicht zu haben :) –

+8

Ich mag LINQ. Liebe es eigentlich. Aber es braucht eine Abhängigkeit von .NET 3.5, die Rendite nicht liefert. Außerdem wird LINQ letztendlich auf dasselbe reduziert wie die Rendite. Was hast du gewonnen? Eine bestimmte C# -Syntax, das ist eine Präferenz. –

+0

So verwenden Sie Ausbeute statt Ihren eigenen Enumerator schreiben ... –

80

Nun, Sie müssten alle Klassen in allen Assemblys auflisten, die in die aktuelle App-Domäne geladen werden. Um dies zu tun, würden Sie die GetAssemblies method auf der AppDomain Instanz für die aktuelle App-Domäne aufrufen.

Von dort würden Sie GetExportedTypes aufrufen (wenn Sie nur öffentliche Typen wollen) oder GetTypes auf jedem Assembly, um die Typen zu erhalten, die in der Baugruppe enthalten sind.

Dann würden Sie die GetCustomAttributes method auf jeder Type Instanz aufrufen und den Typ des gewünschten Attributs übergeben.

Sie können LINQ verwenden, um dies für Sie zu vereinfachen:

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

Die obige Abfrage Sie jede Art erhalten wird mit Ihrem Attribut angewendet wurde, zusammen mit der Instanz des Attributs (n) zugeordnet.

Beachten Sie, dass dieser Vorgang teuer sein kann, wenn Sie eine große Anzahl von Assemblys in Ihre Anwendungsdomäne geladen haben. Sie können Parallel LINQ verwenden, um die Zeit der Operation zu reduzieren, etwa so:

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

Filtern sie auf einem bestimmten Assembly ist einfach:

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

Und wenn die Baugruppe verfügt über eine große Anzahl von Arten in ihm , dann können Sie wieder parallel LINQ verwenden:

+1

Das Aufzählen aller Typen in allen geladenen Assemblys wäre nur sehr langsam und würde Ihnen nicht viel bringen. Es ist möglicherweise auch ein Sicherheitsrisiko. Sie können wahrscheinlich vorhersagen, welche Assemblies die Typen enthalten, an denen Sie interessiert sind. Zählen Sie nur die Typen in diesen auf. –

+0

@Andrew Arnott: Richtig, aber das ist gefragt. Es ist einfach genug, die Abfrage für eine bestimmte Assembly zu löschen. Dies hat auch den zusätzlichen Vorteil, dass Sie die Zuordnung zwischen dem Typ und dem Attribut erhalten. – casperOne

+0

Danke für die Lösung, aber ich bin noch nicht wirklich an diese ganze LINQ-Sache gewöhnt :-) – tomash

8

Wie bereits erwähnt, ist die Reflexion der Weg zu gehen. Wenn Sie dies häufig nennen, empfehle ich sehr, die Ergebnisse zwischenzuspeichern, da Reflexion, insbesondere das Aufzählen durch jede Klasse, ziemlich langsam sein kann.

Dies ist ein Ausschnitt aus meinem Code, der in allen geladenen Baugruppen alle Arten läuft durch: Referenz

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

Andere Antworten GetCustomAttributes.Das Hinzufügen dieser eine als Beispiel für IsDefined

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

Ich glaube, es ist die richtige Lösung, die das Framework beabsichtigte Methode verwenden. –

1

Bei dem Portable .NET limitations verwenden, sollte der folgende Code arbeiten:

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

oder für eine große Anzahl von Baugruppen mittels schlaufenzustandsbasierten 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

Dies ist eine Leistungssteigerung auf der akzeptierten Lösung. Iterating obwohl alle Klassen langsam sein können, weil es so viele gibt. Manchmal können Sie eine gesamte Baugruppe herausfiltern, ohne einen ihrer Typen zu betrachten.

Wenn Sie beispielsweise nach einem selbst deklarierten Attribut suchen, erwarten Sie nicht, dass eine der System-DLLs Typen mit diesem Attribut enthält. Mit der Assembly.GlobalAssemblyCache-Eigenschaft können Sie schnell nach System-DLLs suchen. Als ich dies auf einem echten Programm versuchte, fand ich heraus, dass ich 30.101 Typen überspringen konnte und ich nur 1.983 Typen prüfen musste.

Eine weitere Möglichkeit zum Filtern besteht in der Verwendung von Assembly.ReferencedAssemblies. Wenn Sie Klassen mit einem bestimmten Attribut wünschen und dieses Attribut in einer bestimmten Assembly definiert ist, interessiert Sie vermutlich nur diese Assembly und andere Assemblys, die darauf verweisen. In meinen Tests half dies etwas mehr als das Überprüfen der GlobalAssemblyCache-Eigenschaft.

Ich kombinierte beide und bekam es noch schneller. Der folgende Code enthält beide Filter.

 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) 
Verwandte Themen