Repository navigation
Advanced Usage
Beyond the basic GetClassesByInterface/GetClassesByInheritance calls in Getting Started, a few implementation details affect how you should use this library.
EnumerateClasses<T> is a generic static class, so EnumerateClasses<ICommand> and EnumerateClasses<IPlugin> each get their own independent cache — there's no cross-contamination between different Ts. Within one T, the cache key is (assembly, isInheritanceLookup), so calling both GetClassesByInterface(useCache: true) and GetClassesByInheritance(useCache: true) on the same T caches them separately, and scanning a different assembly gets its own entry too.
// These are cached independently, even though it's the same T:
var byInterface = EnumerateClasses<ICommand>.GetClassesByInterface(useCache: true);
var byInheritance = EnumerateClasses<ICommand>.GetClassesByInheritance(useCache: true);ClearCache() is also per-T — call it on the specific EnumerateClasses<T> whose cache is stale, not globally:
EnumerateClasses<IPlugin>.ClearCache(); // only clears IPlugin's cacheGetClassesByInterface/GetClassesByInheritance construct an instance of every matching type via Activator.CreateInstance. If you only need to inspect the types themselves — for logging, filtering by attribute, or deciding what to register in a DI container — use ListClassesByInterface/ListClassesByInheritance instead, which return Type objects and skip instantiation entirely.
var pluginTypes = EnumerateClasses<IPlugin>.ListClassesByInterface();
foreach (var type in pluginTypes)
{
Console.WriteLine($"Found plugin type: {type.FullName}");
}Both Get* methods filter to !type.IsAbstract && type.GetConstructor(Type.EmptyTypes) != null before attempting instantiation. A matching type without a public parameterless constructor is silently excluded — it won't appear in the results and won't throw. If a type passes that filter but still fails to construct (e.g. its constructor throws), CreateInstance catches MissingMethodException, TargetInvocationException, and TypeLoadException internally and drops that instance from the results rather than propagating the exception.
GetClassesByInheritance/ListClassesByInheritance match on type.BaseType == typeof(T) — only classes that inherit directly from T. A grandchild class (T -> Middle -> Leaf) is not matched by searching for T; you'd need to search for Middle to find Leaf. Interface matching (GetClassesByInterface/ListClassesByInterface) doesn't have this limitation — type.GetInterfaces() includes interfaces implemented anywhere in the hierarchy.
When assembly is omitted, both lookup methods use Assembly.GetCallingAssembly() — the assembly of the code that called EnumerateClasses<T>, not the assembly the library itself lives in. If you wrap a call to EnumerateClasses<T> inside your own helper method, the "calling assembly" becomes your helper's assembly, not your application's — pass assembly explicitly if that distinction matters for your use case.