简体   繁体   English

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

[英]How enumerate all classes with custom class attribute?

Question based on MSDN example .基于MSDN 示例的问题

Let's say we have some C# classes with HelpAttribute in standalone desktop application.假设我们在独立桌面应用程序中有一些带有 HelpAttribute 的 C# 类。 Is it possible to enumerate all classes with such attribute?是否可以枚举具有此类属性的所有类? Does it make sense to recognize classes this way?以这种方式识别类有意义吗? Custom attribute would be used to list possible menu options, selecting item will bring to screen instance of such class.自定义属性将用于列出可能的菜单选项,选择项目将显示此类类的屏幕实例。 Number of classes/items will grow slowly, but this way we can avoid enumerating them all elsewhere, I think.类/项目的数量将缓慢增长,但我认为这样我们可以避免在其他地方枚举它们。

Yes, absolutely.是的,一点没错。 Using Reflection:使用反射:

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

Well, you would have to enumerate through all the classes in all the assemblies that are loaded into the current app domain.好吧,您必须枚举加载到当前应用程序域中的所有程序集中的所有类。 To do that, you would call the GetAssemblies method on the AppDomain instance for the current app domain.为此,您需要对当前应用程序域的AppDomain实例调用GetAssemblies方法

From there, you would call GetExportedTypes (if you only want public types) or GetTypes on each Assembly to get the types that are contained in the assembly.从那里,你会叫GetExportedTypes (如果只想公共类型)或GetTypes每个Assembly来获取包含在集中的类型。

Then, you would call the GetCustomAttributes extension method on each Type instance, passing the type of the attribute you wish to find.然后,您将在每个Type实例上调用GetCustomAttributes扩展方法,传递您希望查找的属性的类型。

You can use LINQ to simplify this for you:您可以使用 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>() };

The above query will get you each type with your attribute applied to it, along with the instance of the attribute(s) assigned to it.上面的查询将为您提供应用了您的属性的每种类型,以及分配给它的属性的实例。

Note that if you have a large number of assemblies loaded into your application domain, that operation could be expensive.请注意,如果将大量程序集加载到应用程序域中,则该操作可能会很昂贵。 You can use Parallel LINQ to reduce the time of the operation (at the cost of CPU cycles), like so:您可以使用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>() };

Filtering it on a specific Assembly is simple:在特定的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>() };

And if the assembly has a large number of types in it, then you can use Parallel LINQ again:如果程序集中有大量类型,那么您可以再次使用 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>() };

Other answers reference GetCustomAttributes .其他答案参考GetCustomAttributes Adding this one as an example of using IsDefined添加此作为使用IsDefined的示例

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

As already stated, reflection is the way to go.如前所述,反思是要走的路。 If you are going to call this frequently, I highly suggest caching the results, as reflection, especially enumerating through every class, can be quite slow.如果你要经常调用它,我强烈建议缓存结果,因为反射,尤其是枚举每个类,可能会很慢。

This is a snippet of my code that runs through all the types in all loaded assemblies:这是我的代码片段,它贯穿所有加载的程序集中的所有类型:

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

This is a performance enhancement on top of the accepted solution.这是在公认的解决方案之上的性能增强。 Iterating though all classes can be slow because there are so many.迭代所有类可能会很慢,因为有很多。 Sometimes you can filter out an entire assembly without looking at any of its types.有时您可以过滤掉整个程序集而无需查看其任何类型。

For example if you are looking for an attribute that you declared yourself, you don't expect any of the system DLLs to contain any types with that attribute.例如,如果您正在寻找您自己声明的属性,您不希望任何系统 DLL 包含具有该属性的任何类型。 The Assembly.GlobalAssemblyCache property is a quick way to check for system DLLs. Assembly.GlobalAssemblyCache 属性是检查系统 DLL 的快速方法。 When I tried this on a real program I found I could skip 30,101 types and I only have to check 1,983 types.当我在一个真实的程序上尝试这个时,我发现我可以跳过 30,101 种类型,而我只需要检查 1,983 种类型。

Another way to filter is to use Assembly.ReferencedAssemblies.另一种过滤方法是使用 Assembly.ReferencedAssemblies。 Presumably if you want classes with a specific attribute, and that attribute is defined in a specific assembly, then you only care about that assembly and other assemblies that reference it.大概如果您想要具有特定属性的类,并且该属性是在特定程序集中定义的,那么您只关心该程序集和引用它的其他程序集。 In my tests this helped slightly more than checking the GlobalAssemblyCache property.在我的测试中,这比检查 GlobalAssemblyCache 属性稍有帮助。

I combined both of these and got it even faster.我将这两者结合起来,并得到了更快的结果。 The code below includes both filters.下面的代码包括两个过滤器。

        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)

In case of the Portable .NET limitations , the following code should work:Portable .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;
    }

or for a large number of assemblies using loop-state based yield return :或者对于使用基于循环状态的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;
                }
            }
        }
    }

This is another version of the code provided by Trade-Ideas philip, I've condensed the code to linq, plugged it into a nice static function which you can just drop in the project.这是由 Trade-Ideas philip 提供的代码的另一个版本,我将代码压缩到 linq,将其插入一个不错的静态函数中,您可以将其放入项目中。

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

I've also added AsParallel() - on my machine with enough cores etc, and with a 'normally' sized project (which is completely subjective), this was the fastest/我还添加了AsParallel() - 在我的机器上有足够的内核等,并且有一个“正常”大小的项目(这是完全主观的),这是最快的/

Without AsParallel() this took 1,5 seconds for about 200 results, and with it, it took about a couple milliseconds - therefore this seems the fastest to me.如果没有AsParallel() ,大约 200 个结果需要 1.5 秒,而有了它,大约需要几毫秒 - 因此这对我来说似乎是最快的。

Note that this skips the assemblies in the GAC.请注意,这会跳过 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;
}

Usage:用法:

var allAttributesInAppDomain = GetAllAttributesInAppDomain<ExportViewAttribute>();

Note if you have only 1 attribute per class, so not multiple, its easier to flatten the result from IEnumerable<IEnumerable<T>> to IEnumerable<T> like so:请注意,如果每个类只有 1 个属性,因此不是多个属性,则更容易将结果从IEnumerable<IEnumerable<T>>展平到IEnumerable<T>如下所示:

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

Remember, this uses IEnumerable so call ToList() to actually run the function.请记住,这使用IEnumerable因此调用ToList()来实际运行该函数。

We can improve on Andrew's answer and convert the whole thing into one LINQ query.我们可以改进 Andrew 的回答并将整个内容转换为一个 LINQ 查询。

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

Ten years ago you would have used Assembly.GetTypes(), but now you should probably do it using Assembly.DefinedTypes : 十年前,您应该使用Assembly.GetTypes(),但现在您应该使用Assembly.DefinedTypes做到这一点:

    foreach (var typeDef in theAssembly.DefinedTypes)
    {
        if (typeDef.CustomAttributes.Any(attrData => attrData.AttributeType == typeof(CustomAttribute)))
        {
            return typeDef.AsType();
        }
    }

The reason is that Assembly.GetTypes() loads every single type for reflection up-front, and System.Reflection.Type objects have a lot of reflection data. 原因是Assembly.GetTypes()加载了每个单一类型以进行预先反射,而System.Reflection.Type对象具有大量反射数据。

Higher C# language versions aggravate the problem. 更高的C#语言版本使问题更加严重。 Assemblies may have hundreds of automatically generated private types just for implementing await/async state machines, anonymous delegates, etc. This is more bad news for users of Assembly.GetTypes() . 程序集可能具有数百个自动生成的专用类型,仅用于实现await/async状态机,匿名委托等。这对于Assembly.GetTypes()用户来说是个坏消息。

Assembly.DefinedTypes instead just loads TypeInfo objects, which hold less data, and require less loading work. Assembly.DefinedTypes只加载TypeInfo对象,该对象保存较少的数据,并且需要较少的加载工作。 You can still filter based on simple type metadata, and finally if you need to load a real System.Reflection.Type object you can do that just for the ones which passed your filter, by calling TypeInfo.AsType() . 您仍然可以基于简单类型的元数据进行过滤,最后,如果您需要加载真实的System.Reflection.Type对象,则可以通过调用TypeInfo.AsType()仅针对通过过滤器的对象执行此操作。

Final handy tip: you often want to filter out abstract and IsGenericTypeDefinition == true types, since you can't instantiate those directly. 最后一个方便的技巧:您经常想过滤掉abstractIsGenericTypeDefinition == true类型,因为您不能直接实例化它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM