views:

2896

answers:

3

Question based on MSDN example: http://msdn.microsoft.com/en-us/library/aa288454(VS.71).aspx

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.

+18  A: 

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
I was just doing this yesterday... `yield` is such a beautiful thing.
Andrew Flanagan
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
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
+7  A: 

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

casperOne
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
@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
Thanks for the solution, but I'm not really accustomed to this whole LINQ thing yet :-)
tomash
Your types don't currently match. Because you're using an anonymous type, you'll need to say `var typesWithMyAttribute`. Alternatively you could just `select t` in the last line
Neil
@Neil: Fixed, thanks. I must have had a previous iteration that simply expected types and not the pair.
casperOne
+1  A: 

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:

   foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) // this is making the assumption that all assemblies we need are already loaded.
   {
    foreach (Type type in assembly.GetTypes())
    {
     var attribs = type.GetCustomAttributes(typeof(MyCustomAttribute), false);
     if (attribs != null && attribs.Length > 0)
     {
      // add to a cache.
rally25rs