Sometimes we need to find all classes or interfaces that derive a certain Type. There are several possible scenarios for this kind of search - For instance, if we are trying to load a plug-in assembly at runtime and wish to initialize all the classes that implement the IPluginBase interface. Using the suggested DerivedHelper makes it a lot easier.
Provided with the following types in an assembly:
Type[] _assemblyTypes = assembly.GetTypes();
We need to decide what sort of a search we wish to perform. These are the possible options:
/// <summary>
/// Specifies flags that control which types will be
/// included or excluded when searching for derived types.
/// </summary>
[Flags]
public enum TypesSearchFlags
{
/// <summary>
/// Get all levels of the inheritance, including abstracts,
/// interfaces, non-abstracts and the base type itself.
/// </summary>
Default = 0,
/// <summary>
/// Include only types that directly derive from the base type.
/// </summary>
DirectDerive = 1,
/// <summary>
/// Include only concrete types, means non-abstract and non-interfaces.
/// </summary>
Concrete = 2,
/// <summary>
/// Exclude the base type.
/// </summary>
ExcludeBase = 4
}
Since this a [Flags] enum, we have 8 possible combinations for searching derived types.
For instance, if we wish to load all plug-in implementations, we would probably set the Concrete and ExcludeBase flags to 1, since we will not be able to create instances of abstract classes in order to load them as plug-ins.
To the code
I love extension methods. That is, I love the RIGHT extension methods.
They should be used safely, but should not be avoided at any cost, as some developers think.
And this seems like a very suiting case for using one - Like this one, for instance:
public static IList<Type> GetDerivedTypes(this Type type,
Assembly assembly,
TypesSearchFlags typesSearchFlags);
As you can see, this method extends the Type class, searching for derived types in a specified assembly according to the specified TypesSearchFlags.
So, with no further a due, let's just dive into the code:
/// <summary>
/// Gets all the types in a given assembly, that derive from the given type,
/// according to the specified search options.
/// </summary>
/// <param name="type">The base type.</param>
/// <param name="assembly">The assembly to search through.</param>
/// <param name="typesSearchFlags">The type search options.</param>
/// <returns>
/// A list of the types that inherit from
/// <paramref name="type"/> in the specified assembly.
/// </returns>
public static IList<Type> GetDerivedTypes(this Type type,
Assembly assembly,
TypesSearchFlags typesSearchFlags)
{
bool _concreteOnly = typesSearchFlags.Is(TypesSearchFlags.Concrete);
bool _excludeBase = typesSearchFlags.Is(TypesSearchFlags.ExcludeBase);
Func<Type, Type, bool> _filterFunction;
if (typesSearchFlags.Is(TypesSearchFlags.DirectDerive))
{
_filterFunction = (__type, __base) => __type.BaseType == __base;
}
else // !typesSearchFlags.Is(TypesSearchFlags.DirectDerive)
{
_filterFunction = (__type, __base) => __base.IsAssignableFrom(__type);
}
IList<Type> _retTypes = new List<Type>();
foreach (Type _type in assembly.GetTypes())
{
// If it is the same type as the base, and ExcludeBase is on, ignore it
if (_excludeBase && _type == type)
{
continue;
}
// If need to include only concrete types and it is not concrete, ignore it
if (_concreteOnly && (_type.IsInterface || _type.IsAbstract))
{
continue;
}
// If it does not pass the filter, ignore it
if (!_filterFunction(_type, type))
{
continue;
}
_retTypes.Add(_type);
}
return _retTypes;
}
If you've read my previous post, the first lines of the method should seem familiar. If not, then here's small detour to explain them: Is is an extension method (yey!) that helps checking whether an enum is or contains (only relevant to [Flags] enumerations) a specified value.
So, after extracting the first two search options:
bool _concreteOnly = typesSearchFlags.Is(TypesSearchFlags.Concrete);
bool _excludeBase = typesSearchFlags.Is(TypesSearchFlags.ExcludeBase);
We continue to evaluating the third option - TypesSearchFlags.DirectDerive - This tells us whether the user wishes to search for direct deriving types, in opposed to types further down the inheritance tree from the type in question.
The third line of code:
Func<Type, Type, bool> _filterFunction;
Declares the filtering delegate, which will be used to determine whether a type in the assembly we are searching matches the TypesSearchFlags.DirectDerive search option. In the following if clause we choose between two possible filters.
The first filter only returns true if the type's direct base type is our type:
_filterFunction = (__type, __base) => __type.BaseType == __base;
Whereas the second delegate uses the IsAssignableFrom method of the Type class to filter types in the assembly:
_filterFunction = (__type, __base) => __base.IsAssignableFrom(__type);
Note(1): This is only the definition of the filter delegate we will be using later in the method. No actual filtering has been made, yet.
Code.Do();
Now, to the actual filtering:
...
IList<Type> _retTypes = new List<Type>();
foreach (Type _type in assembly.GetTypes())
{
// If it is the same type as the base, and ExcludeBase is on, ignore it
if (_excludeBase && _type == type)
{
continue;
}
// If need to include only concrete types and it is not concrete, ignore it
if (_concreteOnly && (_type.IsInterface || _type.IsAbstract))
{
continue;
}
// If it does not pass the filter, ignore it
if (!_filterFunction(_type, type))
{
continue;
}
_retTypes.Add(_type);
}
As you can see, a series of filters is applied to each Type in the queried assembly, if required.
The first part excludes (again, only if required) the base type itself, the second - non-concrete classes - And the third uses the filter delegate to filter types according to the requested derivation type.
If a type passes all three filters - It's a match!
Usage
This extension method can now be used in the following manner:
public void LoadPlugins(Assembly assembly)
{
// All concrete classes that implement the plugin interface.
TypesSearchFlags _searchOptions = TypesSearchFlags.Concrete |
TypesSearchFlags.ExcludeBase;
IEnumerable<Type> _plugins = typeof(IPluginBase).GetDerivedTypes(assembly,
_searchOptions);
foreach (IPluginBase _plugin in _plugins)
{
// TODO: Load plugin.
}
}
Seems pretty simple now, doesn't it?
You can download the DerivedHelper class from here. It also includes the (required) EnumHelper from the previous post.