Chris Marisic
Chris Marisic

Reputation: 33098

Best practices to scan all classes and methods for custom attribute

For the first time ever I've actually needed to do assembly scanning myself manually. I came across C# - how enumerate all classes with custom class attribute? which set me up with

var typesWithMyAttribute =
(from assembly in AppDomain.CurrentDomain.GetAssemblies()
    from type in assembly.GetTypes()
    let attributes = type.GetCustomAttributes(typeof(SomeAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = type, Attributes = attributes.Cast<SomeAttribute>() })
    .ToList();

Which was simple enough to expand out to the method level

var methodsWithAttributes =
    (from assembly in AppDomain.CurrentDomain.GetAssemblies()
    from type in assembly.GetTypes()
    from method in type.GetMethods()
    let attributes = method.GetCustomAttributes(typeof(SomeAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = type, Method = method, 
            Attributes = attributes.Cast<SomeAttribute>() })
    .ToList();

Should I try to combine these 2 to do this in a single scan, or is that just falling into early optimization? (the scanning will only execute on app start)

Is there something different that would be more optimal to do for the scanning of the methods since there are far more methods than types in assemblies?

Upvotes: 5

Views: 5714

Answers (2)

captain numerica
captain numerica

Reputation: 151

Reflection is very slow...

I think you've go the basics there. I'd recommend you change your code slightly to avoid the extra full scan taking place.

If you have to do this more than once, I'd also recommend you consider caching the results for whatever period of time is appropriate.

Sorta like this pseudo-code:

... (optional caches) ...
IDictionary<Type, IEnumerable<Attributes>> typeAttributeCache = new ...
IDictionary<MethodInfo, IEnumerable<Attributes>> methodAttributeCache = new ...

... (in another method or class) ...
foreach assembly in GetAssemblies()
  foreach type in assembly.GetTypes()        
    typeAttributes = typeAttributeCache.TryGet(...) // you know the correct syntax, trying to be brief

    if (typeAttributes is null)
      typeAttributes = type.GetCustomAttributes().OfType<TypeImLookingFor>();
      typeAttributeCache[type] = typeAttributes;

    foreach methodInfo in type.GetMethods()        
      methodAttributes = methodAttributeCache.TryGet(...) // same as above

      if (methodAttributes is null)
        methodAttributes = methodInfo.GetCustomAttributes().OfType<TypeImLookingFor>();
        methodAttributeCache[type] = methodAttributes;

    // do what you need to do

Upvotes: 3

Andrew Bezzub
Andrew Bezzub

Reputation: 16032

I think you can optimize this but it depends on how the attributes are placed on methods and types. If you know that all of your types and/or methods with special attribute are defined in particular assemblies you can scan only these assemblies.

Also you could define some methods, like:

 - IEnumerable<Type> GetAllTypesFromAssemblyByAttribute<TAttribute>(Assembly assembly) where TAttribute : Attribute
 - IEnumerable<MethodInfo> GetAllMethodsFromTypeByAttribute<TAttribute>(Type type) where TAttribute : Attribute

and use these methods in your main scanning method.

So your result scan method could look like:

private void ScanAndDoSmth<TAttribute>(IEnumerable<Assembly> assemblies)
where TAttribute : Attribute
{
    var result =
        from assembly in assemblies
        from type in GetAllTypesFromAssemblyByAttribute<TAttribute>(assembly)
        let attributes = type.GetCustomAttributes(typeof(TAttribute), true)
        where attributes != null && attributes.Length > 0
        select new { Type = type, Attributes = attributes.Cast<TAttribute>();
}

Upvotes: 2

Related Questions