Reputation: 21794
I'm writing a small templating language (much like Razor) and one of the things I have to do on template compilation is to resolve CLR enums based on either (1) a fully qualified name or (2) an unqualified name + a namespace. For example:
namespace Foo.Bar {
public enum MyEnum { A, B }
}
// template:
@using Foo.Bar;
@using System;
...
@Foo.Bar.MyEnum.A // fully qualified
@MyEnum.A // unqualified, but in one of the specified namespaces
My current approach is to scan all assemblies in the current app domain for the enum, which looks something like the following:
string[] namespaces = // parsed from template
string typeName = // parsed from template
string fieldName = // parsed from template
var possibleResolutions = from type in AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic)
.SelectMany(a => a.GetTypes())
where type.IsEnum
from @namespace in namespaces
let fullName = @namespace + '.' + typeName
// the replace is because nested enum types (like we have in AptOne, will have a fullname of namespace.OuterClass+InnerClass)
where type.FullName.Replace('+', '.') == fullName
let field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.Static)
where field != null;
I've found that this can be quite slow on cold startup (dominating all other template compilation time), with nearly all of the time spent in GetTypes(). I'm wondering, is there a faster way to do such lookups?
Note that I'm already caching these results, so I'm not interested in that sort of solution.
Upvotes: 3
Views: 1524
Reputation: 21794
After some experimenting, I've found that Assembly.GetType(string)
is much, much faster than Assembly.GetTypes().Where(...)
. Thus, I've solved my performance problem by making a bunch of targeted name lookups and avoiding looking at all types:
var result = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic)
.SelectMany(
// possible full names is a list which tries replacing each . in the name with a +
// starting from the end
a => possibleFullNames.Select(t => new { t.@namespace, type = a.GetType(t.fullName) })
)
.Where(t => t.type != null);
Upvotes: 1
Reputation: 21729
You could use the Common Compiler Infrastructure to scan the assemblies without reflection, to make a list of the enums. From their site:
The CCI Metadata API allows applications to efficiently analyze or modify .NET assemblies, modules, and debugging (PDB) files. CCI Metadata supports the functionality of the .NET System.Reflection and System.Reflection.Emit APIs, but with much better performance. It also provides additional functionality that is not available in either .NET API.
Then if you need the actual types you can use your list with calls to Assembly.GetType()
.
Upvotes: 1