2009-03-03 2 views
119

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

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

+1

Пример ссылки MSDN - это мертвая ссылка. – MadTigger

ответ

159

Да, абсолютно. Использование Reflection:

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

Согласовано, но в этом случае мы можем сделать это декларативно в соответствии с решением casperOne. Приятно иметь возможность использовать урожай, даже лучше не делать этого :) –

+8

Мне нравится LINQ. Любите это, на самом деле. Но для .NET 3.5 требуется зависимость, которая возвращает доход. Кроме того, LINQ в конечном итоге ломается, по существу, с тем же, что и доходность доходности. Итак, что вы получили? Определенный синтаксис C#, который является предпочтительным. –

+0

Так что используйте доход вместо написания собственного счетчика ... –

80

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

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

Затем вы вызываете GetCustomAttributes method на каждый экземпляр 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>() }; 

И если сборка имеет большое количество типов в нем , то вы снова можете использовать Параллельный 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>() }; 
+1

Перечисление всех типов в сборках _all_ было бы очень медленным и не принесло бы вам многого. Это также потенциально опасно для безопасности. Вероятно, вы можете предсказать, какие сборки будут содержать интересующие вас типы. Просто перечислите типы в них. –

+0

@ Andrew Arnott: Правильно, но это то, о чем просили. Достаточно легко обрезать запрос для конкретной сборки. Это также имеет дополнительное преимущество, предоставляя вам сопоставление между типом и атрибутом. – casperOne

+0

Спасибо за решение, но пока я еще не привык к этой цели LINQ :-) – tomash

8

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

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

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

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

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

Я считаю, что это правильное решение, использующее метод, основанный на каркасе. –

1

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

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

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

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