Как перечислить все классы с помощью специального атрибута класса?

StackOverflow https://stackoverflow.com/questions/607178

Вопрос

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

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

Это было полезно?

Решение

Да, конечно.Использование отражения:

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

Другие советы

Ну, вам придется перечислить все классы во всех сборках, которые загружены в текущий домен приложения.Для этого вам следует позвонить в 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>() };

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

Обратите внимание: если в домен вашего приложения загружено большое количество сборок, эта операция может оказаться дорогостоящей.Вы можете использовать Параллельный 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>() };

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

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

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

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

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

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

Например, если вы ищете атрибут, объявленный вами самостоятельно, вы не ожидаете, что какая-либо из системных 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)

В случае Ограничения портативности .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;
                }
            }
        }
    }

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

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top