Searching for derived types in an assembly

Tuesday, 15 April 2008 11:36 by Yoav Sion

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.

kick it on DotNetKicks.com

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Related posts

Comments

April 17. 2008 17:26

georgem

Very nice. I would just slightly modify the GetDerivedTypes to return IEnumerable<Type> instead of IList<Type> and then add some yields to the game. This would allow some "partial" scanning without evaluating every possible type in assembly.

Then using extension method on IEnumerable<Type> allows you some fluent chaining instead of the enumeration. For example:

foreach(Type t in assembly.GetTypes().DerivedFrom(typeof(someType)).Concrete().ExcludeBase()){ DoYourJob(); }

It's just written from head at morning, hence it can be completelly wrong Smile

georgem

April 17. 2008 18:17

pingback

Pingback from blog.cwa.me.uk

Reflective Perspective - Chris Alcock » The Morning Brew #76

blog.cwa.me.uk

April 17. 2008 23:45

Alun Harford

It's a nice pattern.

1 line of LINQ seems easier though. The lambda probably wants getting rid of too.

public static IEnumerable<Type> GetDerivedTypes(this Type type, Assembly assembly, TypesSearchFlags typesSearchFlags)
{
return assembly.GetTypes()
.Where(t => !(
(typesSearchFlags.Is(TypesSearchFlags.ExcludeBase) && t == type) ||
(typesSearchFlags.Is(TypesSearchFlags.Concrete) && t.IsAbstract) ||
!(typesSearchFlags.Is(TypesSearchFlags.DirectDerive) ?
((Type typeToTest) => typeToTest.BaseType == type) :
((Type typeToTest) => type.IsAssignableFrom(typeToTest))(t))));
}

Alun Harford

April 18. 2008 03:56

Adrian Aisemberg

Alun, after writing that long LINQ, how do you test it? What you'll do when a new feature should be added? How do you debug it?

LINQ is a cool alternative sometimes, but do not over-use it.

I'll prefer writing long reliable, testable, debuggable and maintainable code.

Adrian Aisemberg

April 19. 2008 12:16

pingback

Pingback from alvinashcraft.com

Dew Drop - April 19, 2008 | Alvin Ashcraft's Morning Dew

alvinashcraft.com

Add comment


(Will show your Gravatar icon)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

July 23. 2008 06:29