Самый эффективный способ получить все типы в AppDomain, помеченные определенным атрибутом?

#c# #reflection

#c# #отражение

Вопрос:

Если я это сделаю, я перечислю все типы в своей программе:

 List<SerializableAttribute> attributes=new List<SerializableAttribute>() ;
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    foreach (Type type in assembly.GetTypes())
    {
        attributes.AddRange(
                            type.GetCustomAttributes(false)
                            .OfType<SerializableAttribute>()
                            .ToList());
    }
}
  

Индексируются ли метаданные, поставляемые с .NET dll, чтобы позволить мне делать что-то вроде:

 List<SerializableAttribute> attributes = typeof(SerializableAttribute)
                                         .GetClassesIAmDefinedOn();
  

Есть ли другой вариант, который я не рассматриваю?

(SerializableAttribute — это просто пример)

Комментарии:

1. Чего именно вы пытаетесь достичь? Настройка [InternalsVisibleTo(...)] везде?

2. Я не пытаюсь изменить какие-либо значения атрибутов. Я поддерживаю систему (которую я не писал), и она сканирует все типы на наличие определенных атрибутов, чтобы каким-то образом отобразить их в списке в системе, например DisplayNameAttribute .

Ответ №1:

Ну, использование LINQ больше и использование IsDefined как минимум делает код лучше (и извлекает типы, а не атрибуты …)

 var types = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
             from type in assembly.GetTypes()
             where Attribute.IsDefined(type, typeof(SerializableAttribute))
             select type).ToList();
  

Теперь, вы спросили об эффективности — сколько времени это займет? Сколько времени это приемлемо для этого? Вы часто вызываете это? (Это может показаться странным.)

Также обратите внимание, что он включает только те сборки, которые уже были загружены — может быть ссылочная сборка, которая еще не загружена; имеет ли значение, что она не была выбрана?

Комментарии:

1. Это может занять всего <50/100 мс для программы типа 20K , но мне действительно было интересно, есть ли в метаданных какой-то кэш, который не позволил бы мне перечислять все типы 20K любым способом (для поиска менее 10 мс).

2. @Mick единственный способ сделать это — явно сохранить эти данные отдельно.

3. @Marc это вообще стоит рассмотреть? т. Е. Это так же просто, как перевернуть какой-то непонятный флаг или написать <10 строк кода (к которым я больше никогда не буду прикасаться), чтобы изменить процесс компиляции?

4. Спасибо. Джон… это сработало хорошо. Один трюк, который вы, возможно, захотите также предоставить, или давайте просто упомянем его здесь … если вы заботитесь только о классе, который вы пометили атрибутом, а не о каком-либо из его потомков, вы должны использовать 3-й параметр для IsDefined и передать false .

5. @RichardB: Думаю, я оставлю это в вашем комментарии — я не хочу вдаваться в подробности 🙂

Ответ №2:

Наиболее эффективная вещь, которую можно использовать здесь, как правило, это Attribute.IsDefined(...) , хотя в конкретном случае [Serializable] , type.IsSerializable быстрее (в данном случае он фактически не сохраняется как атрибут — он имеет специальную обработку в компиляторе, сопоставление с флагом CLI).

Комментарии:

1. Должен ли я выполнять «foreach(введите в appdomain) { … Атрибут. IsDefined(тип) … }» или есть способ, которым я могу это сделать, не перечисляя все типы?

2. @Mick да, вы должны перечислять типы.

Ответ №3:

Нет, это не так. И остерегайтесь GetCustomAttributes . Это очень дорого и неэффективно кэшируется. AppDomain.Текущий.Домен.GetAssemblies также очень дорого.

Чтобы делать подобные вещи, я храню кеш в словаре

 var cache = new Dictionary<Assembly,Attribute[]>();

foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    var attributes = new List<SerializableAttribute>();
    foreach (Type type in assembly.GetTypes())
    {
        attributes.AddRange(
                            type.GetCustomAttributes(false)
                            .OfType<SerializableAttribute>()
                            .ToList());
    }
    cache[assembly] = attributes.ToArray();
}
  

Ответ №4:

Вы могли бы сделать либо:

 var assem = // get assembly:
var types = assem.GetTypes().Where(t => t.IsDefined(typeof(SerializableAttribute)));
  

Или, если вы хотите сделать это наоборот:

 public static IEnumerable<Type> WhereDefinedOn(this Type type, IEnumerable<Type> types)
{
    if (!typeof(Attribute).IsAssignableFrom(type))
        throw new InvalidOperationException("Only attribute types are supported.");

    return types.Where(t => t.IsDefined(type));
}
  

Который вы можете использовать как:

 var allTypes = assem.GetTypes();
var filteredTypes = typeof(SerializableAttribute).WhereDefinedOn(allTypes);