如何枚举具有自定义类属性的所有类?


151

基于MSDN示例的问题

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

Answers:


205

是的,一点没错。使用反射:

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

7
同意,但是在这种情况下,我们可以按照casperOne的解决方案声明性地进行操作。能够使用yield很好,不必使用它也更好:)
Jon Skeet

9
我喜欢LINQ。爱它,实际上。但是它依赖于.NET 3.5,而收益率却没有。而且,LINQ最终分解为与收益回报基本相同的东西。那你获得了什么?一种特定的C#语法,即首选项。
安德鲁·阿诺特

1
@AndrewArnott最少的代码行与性能无关,它们仅是可读性和可维护性的贡献者。我质疑这样的说法:它们分配的对象最少,性能会更快(尤其是没有经验证明);您基本上已经编写了Select扩展方法,并且编译器将生成状态机,就像您Select由于使用调用时一样yield return。最后,任何的性能提升可能在大多数情况下获得通过micro-优化。
casperOne 2014年

1
非常正确,@ casperOne。差异很小,尤其是与反射本身的重量相比。可能永远不会出现在性能跟踪中。
Andrew Arnott 2014年

1
当然,Resharper说“可以将foreach循环转换为LINQ表达式”,如下所示:assembly.GetTypes()。Where(type => type.GetCustomAttributes(typeof(HelpAttribute),true).Length> 0);
大卫·巴罗斯

107

好吧,您将必须枚举加载到当前应用程序域中的所有程序集中的所有类。为此,您可以在当前应用程序域的实例上调用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>() };

上面的查询将为您提供应用了属性的每种类型,以及分配给它的属性的实例。

请注意,如果您在应用程序域中加载了大量程序集,则该操作可能会很昂贵。您可以使用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>() };

1
枚举所有类型的所有加载的程序集也只是非常缓慢,无法获得你多少。这也可能是安全隐患。您可能可以预测哪些程序集将包含您感兴趣的类型。只需枚举其中的类型即可。
安德鲁·阿诺特

@Andrew Arnott:正确,但这就是要求的内容。删除特定程序集的查询很容易。这还具有为您提供类型和属性之间的映射的附加好处。
casperOne

1
您可以使用System.Reflection.Assembly.GetExecutingAssembly()在当前程序集中使用相同的代码
Chris Moschini 2012年

@ChrisMoschini是的,可以,但是您可能并不总是希望扫描当前程序集。最好保持打开状态。
casperOne 2012年

我已经做过很多次了,并且没有很多方法可以提高效率。您可以跳过Microsoft程序集(它们使用相同的密钥签名,因此很容易避免使用AssemblyName。您可以将结果缓存在静态中,该结果对于加载程序集的AppDomain是唯一的(必须缓存完整的您检查的程序集的名称(以防在此同时加载其他程序集的名称)。在调查此属性内属性类型的加载实例的缓存时,发现自己在这里。不确定该模式,不确定何时实例化等等。


11

如前所述,反思是必须走的路。如果您经常调用它,我强烈建议缓存结果,因为反射,尤其是在每个类中进行枚举可能会很慢。

这是我的代码的片段,该代码遍历所有已加载程序集中的所有类型:

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

9

这是在公认解决方案之上的性能增强。迭代所有类可能很慢,因为有很多类。有时,您可以筛选出整个程序集而无需查看其任何类型。

例如,如果要查找声明为自己的属性,则不要期望任何系统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)

4

便携式.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;
                }
            }
        }
    }

0

我们可以改进安德鲁的答案,并将整个内容转换为一个LINQ查询。

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.