2009-03-03 125 views
119

问题根据MSDN example如何枚举具有自定义类属性的所有类?

假设我们在独立桌面应用程序中有一些C#类和HelpAttribute。有没有可能枚举具有这种属性的所有类?以这种方式识别班级有意义吗?自定义属性将用于列出可能的菜单选项,选择项目将带来此类的实例。类/项目的数量将会增长缓慢,但这样我们可以避免在其他地方列举它们,我想。

+1

MSDN示例链接是一个死链接。 – MadTigger 2017-10-11 00:03:58

回答

159

是的,绝对。使用反射:

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的解决方案以声明方式进行。很高兴能够使用良率,更好的是不必:) – 2009-03-03 17:23:31

+8

我喜欢LINQ。其实,爱它。但是它需要依赖于.NET 3.5,而不是返回。此外,LINQ最终分解成与收益回报基本相同的东西。那么你有什么收获?特定的C#语法,这是一个首选项。 – 2009-03-03 18:14:55

+0

因此,使用yield来代替编写自己的枚举器...... – 2014-03-19 07:05:53

80

那么,你将不得不枚举通过加载到当前应用程序域的所有程序集中的所有类。为此,您可以在当前应用程序域的AppDomain实例上调用GetAssemblies method

从那里,你可以拨打GetExportedTypes(如果你只是想要公共类型)或GetTypes在每个Assembly获得包含在程序集中的类型。

然后,您将调用每个Type实例上的GetCustomAttributes method,传递您希望查找的属性的类型。

您可以使用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>() }; 
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; 
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)