172

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.

c24w
  • 7,421
  • 7
  • 39
  • 47
tomash
  • 12,742
  • 15
  • 64
  • 81
  • [This](https://stackoverflow.com/questions/69496590/how-to-read-custom-attribute-parameters-and-values-attached-to-a-class-or-method) might be also helpful. – Matt Oct 15 '21 at 07:38

8 Answers8

232

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;
        }
    }
}
Andrew Arnott
  • 80,040
  • 26
  • 132
  • 171
  • 7
    Agreed, but in this case we can do it declaratively as per casperOne's solution. It's nice to be able to use yield, it's even nicer not to have to :) – Jon Skeet Mar 03 '09 at 17:23
  • 10
    I like LINQ. Love it, actually. But it takes a dependency on .NET 3.5, which yield return does not. Also, LINQ eventually breaks down to essentially the same thing as yield return. So what have you gained? A particular C# syntax, that is a preference. – Andrew Arnott Mar 03 '09 at 18:14
  • True, but guess which of the three options requires the fewest and shortest lines of code. And allocates the fewest objects (in other words runs the fastest). :) – Andrew Arnott Mar 19 '14 at 15:26
  • 2
    @AndrewArnott Fewest and shortest lines of code are irrelevant to performance, they're only possible contributors to readability and maintainability. I challenge the statement that they allocate the fewest objects and performance will be faster (especially without empirical proof); you've basically written the `Select` extension method, and the compiler will generate a state machine just as it would if you called `Select` because of your use of `yield return`. Finally, any performance gains that *might* be obtained in the majority of cases be micro-optimizations. – casperOne Jun 05 '14 at 12:04
  • We agree on readability. My point about 'fastest' was around object allocations. Other answers here all call the same reflection APIs, so the only real difference is the code around it. Using linq expressions will tend to create one or two objects per sub-expression. My solution will create the IEnumerable object only. So it's more efficient, and therefore faster. But certainly, it's probably not important here. But getting better perf and smaller code besides, when it's still quite readable (not like perl), is a win all around. – Andrew Arnott Jun 06 '14 at 22:45
  • Your statement "my solution will create the `IEnumerable` object only" is incorrect. The `yield return` forces the creation of a state machine, and you'll return the same number of objects as if you called `Select`. – casperOne Jul 21 '14 at 11:45
  • 1
    @casperOne, you mention my way creates a state machine. That state machine _is_ the `IEnumerable` that I said it creates. Using `Select` means that you are allocating delegates and closures as well, which my method does not require. – Andrew Arnott Jul 24 '14 at 14:14
  • 1
    @AndrewArnott Very true. At the same time, a micro-optimization. – casperOne Jul 25 '14 at 13:29
  • 2
    Quite right, @casperOne. A very minor difference, especially compared with the weight of reflection itself. Probably would never come up in a perf trace. – Andrew Arnott Jul 27 '14 at 20:23
  • 1
    Of course Resharper says "that foreach loop can be converted into a LINQ expression" which looks like this: assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0); – David Barrows Oct 13 '16 at 14:13
121

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 (at the cost of CPU cycles), 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>() };
casperOne
  • 73,706
  • 19
  • 184
  • 253
  • 1
    Enumerating all types in _all_ loaded assemblies would just be very slow and not gain you much. It's also potentially a security risk. You can probably predict which assemblies will contain the types you're interested in. Just enumerate the types in those. – Andrew Arnott Mar 03 '09 at 17:02
  • 1
    @Andrew Arnott: Correct, but this is what was asked for. It's easy enough to prune the query down for a particular assembly. This also has the added benefit of giving you the mapping between the type and the attribute. – casperOne Mar 03 '09 at 17:14
  • 1
    You can use the same code on just the current assembly with System.Reflection.Assembly.GetExecutingAssembly() – Chris Moschini Jun 05 '12 at 02:13
  • 1
    @ChrisMoschini Yes, you can, but you might not always want to scan the current assembly. Better to leave it open. – casperOne Jun 05 '12 at 02:50
  • I've done this many times, and there aren't many ways to make it efficient. You can skip microsoft assemblies (they are signed with the same key, so they are pretty easy to avoid using AssemblyName. You can cache results within a static, which is unique to the AppDomain in which the assemblies are loaded (have to cache the full names of the assemblies you checked in case others are loaded in the meantime). Found myself here as I'm investigating caching loaded instances of an attribute type within the attribute. Not sure of that pattern, not sure when they are instantiated, etc. –  Aug 08 '12 at 14:29
  • 1
    @Will The point is, you can choose *which* assemblies you want to scan easily, but agreed, if your realm is the realm of *all* assemblies, then you're in for a bit of a wait. However, [Parallel LINQ](http://msdn.microsoft.com/en-us/magazine/cc163329.aspx) would help here, and I've updated the answer to reflect that. – casperOne Aug 08 '12 at 14:31
  • @casperOne: Nice. Also, a bit more research here and found that [attributes are instantiated only when you ask for them](http://stackoverflow.com/questions/1168535/when-is-a-custom-attributes-constructor-run). There is no way but to cull out what you know you don't need, cache what you find, and ensure the validity of the cache when you go back to it. –  Aug 08 '12 at 14:42
  • @Will To make matters worse, attributes are actually not cached at all. They are created on the fly from the embedded byte data in the assembly. The reason being that you shouldn't be able to update a public property on an attribute (even though people do that) because that affects the metadata, which is static throughout the app domain lifetime. Because of this, every time you call `GetCustomAttributes` it's creating new instances of the attributes for you. – casperOne Aug 08 '12 at 15:31
40

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;
Jay Walker
  • 4,654
  • 5
  • 47
  • 53
12

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.
        }
    }
}
joce
  • 9,624
  • 19
  • 56
  • 74
CodingWithSpike
  • 42,906
  • 18
  • 101
  • 138
10

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)
Trade-Ideas Philip
  • 1,067
  • 12
  • 21
4

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;
                }
            }
        }
    }
Lorenz Lo Sauer
  • 23,698
  • 16
  • 85
  • 87
1

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.

Original: 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/

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.

Note that this skips the assemblies in the 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:

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

Remember, this uses IEnumerable so call ToList() to actually run the function.

sommmen
  • 6,570
  • 2
  • 30
  • 51
0

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);
    }
Tachyon
  • 2,171
  • 3
  • 22
  • 46