Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 345
Chris Marisic Avatar asked Mar 10 '11 22:03

Chris Marisic


2 Answers

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
like image 103
captain numerica Avatar answered Sep 28 '22 23:09

captain numerica


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>();
}
like image 44
Andrew Bezzub Avatar answered Sep 28 '22 21:09

Andrew Bezzub