Как перечислить все классы с пользовательским атрибутом класса? - PullRequest
138 голосов
/ 03 марта 2009

Вопрос на основе Пример MSDN .

Допустим, у нас есть несколько классов C # с HelpAttribute в автономном настольном приложении. Можно ли перечислить все классы с таким атрибутом? Имеет ли смысл распознавать классы таким образом? Пользовательский атрибут будет использоваться для отображения списка возможных вариантов меню, выбор пункта приведет к выводу на экран экземпляра такого класса. Количество классов / предметов будет расти медленно, но, таким образом, мы можем избежать их перечисления в другом месте, я думаю.

Ответы [ 7 ]

184 голосов
/ 03 марта 2009

Да, абсолютно. Используя отражение:

static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
    foreach(Type type in assembly.GetTypes()) {
        if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
            yield return type;
        }
    }
}
95 голосов
/ 03 марта 2009

Ну, вам придется перечислять все классы во всех сборках, которые загружены в текущий домен приложения. Для этого необходимо вызвать метод GetAssemblies в экземпляре AppDomain для текущего домена приложения.

Оттуда вы бы позвонили GetExportedTypes (если вы хотите только публичные типы) или GetTypes на каждом Assembly, чтобы получить типы, содержащиеся в сборке.

Затем вы вызываете метод GetCustomAttributes для каждого экземпляра Type, передавая тип атрибута, который вы хотите найти.

Вы можете использовать LINQ, чтобы упростить это для вас:

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

Приведенный выше запрос даст вам каждый тип с примененным к нему атрибутом, а также экземпляр присвоенного ему атрибута (ов).

Обратите внимание, что если в домен приложения загружено большое количество сборок, эта операция может быть дорогостоящей. Вы можете использовать Parallel LINQ для сокращения времени выполнения операции, например:

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

Фильтрация по определенному Assembly проста:

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

И если в сборке много типов, вы можете снова использовать 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>() };
31 голосов
/ 06 февраля 2013

Ссылка на другие ответы GetCustomAttributes . Добавление этого в качестве примера использования IsDefined

Assembly assembly = ...
var typesWithHelpAttribute = 
        from type in assembly.GetTypes()
        where type.IsDefined(typeof(HelpAttribute), false)
        select type;
10 голосов
/ 03 марта 2009

Как уже говорилось, отражение - это путь. Если вы собираетесь это часто называть, я настоятельно рекомендую кэшировать результаты, поскольку рефлексия, особенно перечисление в каждом классе, может быть довольно медленной.

Это фрагмент моего кода, который проходит через все типы во всех загруженных сборках:

// 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.
        }
    }
}
6 голосов
/ 31 декабря 2016

Это повышение производительности поверх принятого решения. Итерации, хотя все классы могут быть медленными, потому что их так много. Иногда вы можете отфильтровать всю сборку, не глядя ни на один из ее типов.

Например, если вы ищете атрибут, который вы объявили сами, вы не ожидаете, что какие-либо системные библиотеки DLL будут содержать какие-либо типы с этим атрибутом. Свойство Assembly.GlobalAssemblyCache - это быстрый способ проверки системных библиотек DLL. Когда я попробовал это в реальной программе, я обнаружил, что могу пропустить 30 101 тип, и мне нужно только проверить 1 983 типа.

Другим способом фильтрации является использование Assembly.ReferencedAssemblies. Предположительно, если вам нужны классы с определенным атрибутом, и этот атрибут определен в конкретной сборке, вам нужно заботиться только об этой сборке и других сборках, которые на нее ссылаются. В моих тестах это помогло немного больше, чем проверка свойства GlobalAssemblyCache.

Я объединил их обоих и получил еще быстрее. Код ниже включает оба фильтра.

        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)
3 голосов
/ 27 июля 2016

В случае ограничений Portable .NET должен работать следующий код:

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

или для большого количества сборок, использующих основанное на цикле состояние 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;
                }
            }
        }
    }
0 голосов
/ 30 ноября 2018

Мы можем улучшить ответ Эндрю и преобразовать все это в один запрос LINQ.

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...