Как перечислять все классы с пользовательским атрибутом класса?
Вопрос основан на примере MSDN.
Допустим, у нас есть несколько классов С# с HelpAttribute в автономном настольном приложении. Можно ли перечислить все классы с таким атрибутом? Имеет ли смысл распознавать классы таким образом? Пользовательский атрибут будет использоваться для отображения списка возможных вариантов меню, выбор пункта приведет к выводу на экран экземпляра такого класса. Количество классов/предметов будет расти медленно, но, таким образом, мы можем избежать их перечисления в другом месте, я думаю.
Ответы
Ответ 1
Да, абсолютно. Использование Reflection:
static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
foreach(Type type in assembly.GetTypes()) {
if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
yield return type;
}
}
}
Ответ 2
Что ж, вам нужно будет перечислить все классы во всех сборках, которые загружены в текущий домен приложения. Для этого необходимо вызвать метод GetAssemblies
в экземпляре AppDomain
для текущего домена приложения.
Оттуда вы будете вызывать GetExportedTypes
(если вам нужны только открытые типы) или GetTypes
для каждой Assembly
чтобы получить типы, содержащиеся в сборке.
Затем вы должны вызывать GetCustomAttributes
расширения 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>() };
Ответ 3
Другие ответы ссылаются на GetCustomAttributes. Добавление этого примера в качестве примера использования IsDefined
Assembly assembly = ...
var typesWithHelpAttribute =
from type in assembly.GetTypes()
where type.IsDefined(typeof(HelpAttribute), false)
select type;
Ответ 4
Как уже говорилось, рефлексия - это путь. Если вы собираетесь называть это часто, я настоятельно рекомендую кэшировать результаты, поскольку отражение, особенно перечисление через каждый класс, может быть довольно медленным.
Это фрагмент кода, который проходит через все типы во всех загруженных сборках:
// 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.
}
}
}
Ответ 5
Это повышение производительности поверх принятого решения. Итерация, хотя все классы могут быть медленными, потому что их так много. Иногда вы можете отфильтровать всю сборку, не глядя на какие-либо ее типы.
Например, если вы ищете атрибут, который вы объявили сами, вы не ожидаете, что какая-либо из системных 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)
Ответ 6
В случае переносных ограничений .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;
}
}
}
}
Ответ 7
Мы можем улучшить ответ Andrew и преобразовать все это в один запрос LINQ.
public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
{
return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
}