How enumerate all classes with custom class attribute?

烈酒焚心 提交于 2019-11-26 05:57:21

问题


Question based on MSDN example.

Let\'s say we have some C# classes with HelpAttribute in standalone desktop application. 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.


回答1:


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



回答2:


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.

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.

Then, you would call the GetCustomAttributes extension method on each Type instance, passing the type of the attribute you wish to find.

You can use LINQ to simplify this for you:

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, like so:

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 = ...;

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:

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



回答3:


Other answers reference GetCustomAttributes. Adding this one as an example of using IsDefined

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



回答4:


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



回答5:


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. The Assembly.GlobalAssemblyCache property is a quick way to check for system DLLs. 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.

Another way to filter is to use 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.

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)



回答6:


In case of the Portable .NET limitations, the following code should work:

    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:

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



回答7:


We can improve on Andrew's answer and convert the whole thing into one LINQ query.

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


来源:https://stackoverflow.com/questions/607178/how-enumerate-all-classes-with-custom-class-attribute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!