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

问题描述 投票:0回答:9

基于 MSDN 示例的问题。

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

c# class attributes custom-attributes enumerate
9个回答
235
投票

是的,绝对如此。使用反射:

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

121
投票

嗯,您必须枚举加载到当前应用程序域的所有程序集中的所有类。为此,您可以在当前应用程序域的

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 来减少操作时间(以 CPU 周期为代价),如下所示:

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>() };

40
投票

其他答案参考GetCustomAttributes。添加此作为使用 IsDefined

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

12
投票

正如已经说过的,反思是必经之路。如果您要频繁调用此函数,我强烈建议缓存结果,因为反射,尤其是枚举每个类,可能会非常慢。

这是我的代码片段,它贯穿所有加载的程序集中的所有类型:

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

11
投票

这是在已接受的解决方案之上的性能增强。迭代所有类可能会很慢,因为数量太多。有时,您可以过滤掉整个程序集,而无需查看其任何类型。

例如,如果您正在查找自己声明的属性,则您不希望任何系统 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;
                }
            }
        }
    }

1
投票

我们可以改进 Andrew 的答案,并将整个问题转换为一个 LINQ 查询。

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }

1
投票

这是 Trade-Ideas philip 提供的代码的另一个版本, 我已将代码压缩为 linq,将其插入到一个很好的静态函数中,您可以将其放入项目中。

原文: https://stackoverflow.com/a/41411243/4122889

我还添加了

AsParallel()
- 在我的机器上具有足够的核心等,并且对于“正常”大小的项目(完全主观),这是最快的/

如果没有

AsParallel()
,大约需要 1.5 秒才能得到大约 200 个结果,有了它,大约需要几毫秒 - 因此这对我来说似乎是最快的。

请注意,这会跳过 GAC 中的程序集。

private static IEnumerable<IEnumerable<T>> GetAllAttributesInAppDomain<T>()
{
    var definedIn = typeof(T).Assembly.GetName().Name;
    var assemblies = AppDomain.CurrentDomain.GetAssemblies();

   var res = assemblies.AsParallel()
        .Where(assembly => (!assembly.GlobalAssemblyCache) && ((assembly.GetName().Name == definedIn) ||
                                                               assembly.GetReferencedAssemblies()
                                                                   .Any(a => a.Name == definedIn))
            )
        .SelectMany(c => c.GetTypes())
        .Select(type => type.GetCustomAttributes(typeof(T), true)
            .Cast<T>()
            )
        .Where(c => c.Any());

    return res;
}

用途:

var allAttributesInAppDomain = GetAllAttributesInAppDomain<ExportViewAttribute>();

请注意,如果每个类只有 1 个属性,而不是多个属性,则更容易将结果从

IEnumerable<IEnumerable<T>>
展平为
IEnumerable<T>
,如下所示:

var allAttributesInAppDomainFlattened = allAttributesInAppDomain.SelectMany(c => c);

请记住,这使用

IEnumerable
,因此调用
ToList()
来实际运行该函数。


0
投票

进一步Andrew的回答

以下是我在这种情况下使用的两种扩展方法。 它们返回单独类型或程序集中类型的特定属性的枚举。

public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Type type) where TAttribute : Attribute
{
    if (type.GetCustomAttributes<TAttribute>(true).Any())
        foreach (var atribute in type.GetCustomAttributes<TAttribute>(true))
            yield return atribute;
}
public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Assembly assembly) where TAttribute : Attribute
{
    return assembly.GetTypes().Where(type => type.GetCustomAttributes<TAttribute>(true).Any()).SelectMany(x => GetAttributes<TAttribute>(x)).AsEnumerable();
}

可以进一步增强这些以使用表达式/谓词返回所需的结果。

public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Type type, Func<TAttribute, bool> predicate) where TAttribute : Attribute
{
    if (type.GetCustomAttributes<TAttribute>(true).Any())
    {
        var attributes = type.GetCustomAttributes<TAttribute>(true).Where(predicate);
        foreach (var atribute in attributes) yield return atribute;
    }
}
public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Assembly assembly, Func<TAttribute, bool> predicate) where TAttribute : Attribute => assembly.GetTypes()
                                                                                                                                                                  .Where(type => type.GetCustomAttributes<TAttribute>(true).Any())
                                                                                                                                                                  .SelectMany(x => GetAttributes<TAttribute>(x))
                                                                                                                                                                  .Where(predicate)
                                                                                                                                                                  .AsEnumerable();

以上这些方法是通用的,需要属性类型来获取类中的相关属性。例如

    var modules = typeof(IEntity).Assembly.GetAttributes<Module>();
    var modules = typeof(MyClass).GetAttributes<Module>();
© www.soinside.com 2019 - 2024. All rights reserved.