it-swarm.com.de

Wie werden alle Klassen mit benutzerdefinierten Klassenattributen aufgelistet?

Frage basierend auf MSDN-Beispiel .

Nehmen wir an, wir haben einige C # -Klassen mit HelpAttribute in einer eigenständigen Desktopanwendung. Ist es möglich, alle Klassen mit einem solchen Attribut aufzulisten? Ist es sinnvoll, Klassen auf diese Weise zu erkennen? Benutzerdefinierte Attribute werden verwendet, um mögliche Menüoptionen aufzulisten. Wenn Sie ein Element auswählen, wird eine Instanz dieser Klasse angezeigt. Die Anzahl der Klassen/Gegenstände wird langsam zunehmen, aber auf diese Weise können wir vermeiden, sie an anderer Stelle aufzählen, denke ich.

134
tomash

Ja absolut. Reflexion verwenden:

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

Nun, Sie müssten alle Klassen in allen Assemblys auflisten, die in die aktuelle App-Domäne geladen werden. Dazu rufen Sie die GetAssemblies-Methode in der AppDomain -Instanz für die aktuelle App-Domäne auf.

Von dort aus würden Sie GetExportedTypes (wenn Sie nur öffentliche Typen wünschen) oder GetTypes auf jedem Assembly aufrufen, um die in der Assembly enthaltenen Typen abzurufen.

Dann würden Sie die GetCustomAttributes-Methode in jeder Type - Instanz aufrufen und den Typ des Attributs übergeben, das Sie suchen möchten.

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 liefert Ihnen jeden Typ, auf den Ihr Attribut angewendet wird, sowie die Instanz des Attributs, das ihm zugewiesen ist.

Wenn eine große Anzahl von Assemblys in Ihre Anwendungsdomäne geladen ist, kann dieser Vorgang teuer sein. Sie können Parallel LINQ verwenden, um die Zeit der Operation zu reduzieren, z.

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

Das Filtern nach einer 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>() };

Wenn die Assembly eine große Anzahl von Typen enthält, können Sie Parallel LINQ erneut verwenden:

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

Andere Antworten referenzieren GetCustomAttributes . Hinzufügen eines Beispiels für die Verwendung von IsDefined

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

Wie bereits gesagt, ist das Nachdenken der Weg. Wenn Sie dies häufig anrufen, empfehle ich dringend, die Ergebnisse im Cache zu speichern, da die Reflexion, insbesondere das Auflisten durch jede Klasse, sehr langsam sein kann.

Dies ist ein Auszug aus meinem Code, der alle Typen in allen geladenen Baugruppen durchläuft:

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

Dies ist eine Leistungssteigerung zusätzlich zur akzeptierten Lösung. Das Durchlaufen aller Klassen kann jedoch langsam sein, da es so viele gibt. Manchmal können Sie eine gesamte Baugruppe herausfiltern, ohne einen ihrer Typen zu betrachten. 

Wenn Sie beispielsweise nach einem Attribut suchen, das Sie selbst deklariert haben, erwarten Sie nicht, dass die System-DLLs Typen mit diesem Attribut enthalten. Mit der Assembly.GlobalAssemblyCache-Eigenschaft können Sie schnell nach System-DLLs suchen. Als ich dies mit einem echten Programm ausprobierte, stellte ich fest, dass ich 30.101 Typen überspringen konnte und nur noch 1.883 Typen prüfen muss.

Eine andere Möglichkeit zum Filtern ist die Verwendung von Assembly.ReferencedAssemblies. Vermutlich, wenn Sie Klassen mit einem bestimmten Attribut haben möchten und dieses Attribut in einer bestimmten Assembly definiert ist, kümmern Sie sich nur um diese Assembly und andere Assemblys, die darauf verweisen. In meinen Tests hat dies etwas mehr geholfen als das Überprüfen der GlobalAssemblyCache-Eigenschaft.

Ich habe beide kombiniert und es noch schneller gemacht. 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)
6

Bei den Einschränkungen von Portable .NET sollte der folgende Code funktionieren:

    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 Assemblys, die auf dem Loop-Status basieren, 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;
                }
            }
        }
    }
2
Lorenz Lo Sauer

Wir können Andrews Antwort verbessern und das Ganze in eine LINQ-Abfrage konvertieren.

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return Assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
0
Tachyon