K Cartlidge

C#/DotNet. Go. Node. Python/Flask. Elixir.

Getting instances of all implementations of an interface in C#

Dependency injection is not always the answer

Dependency injection is great. You know, inversion of control, constructor injection and all the other buzzwords.

Sometimes however it can be overkill.

I recently knocked up a tool that, having no UI and just a progress indicator, was ideally suited to a console application. One of the things it did was to create an export in a variety of formats (text, RTF and EPUB). I wanted it to be easy to add new export implementations, ideally without needing setup code.

Naturally I coded to an interface (IGenerator) and all the exporters implemented it. What I needed was a way to gather all things that implement that interface and run them in turn, in such a way that if new ones were added they were automatically picked up (the nature of the code meant that an actual IOC container setup was not needed).

The quick solution

Below is the class I created. It's called like this:

static List<IGenerator> generators = ImplementationLocator.GetImplementations<IGenerator>();

Its return value is a collection of instances of each implementation that I can call like this:

foreach (var generator in generators) generator.Generate(document, folder);

Without further ado (and help yourself to this as it can certainly be improved), here's the actual class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace KCartlidge.Support
{
    /// <summary>
    /// Finds and creates instances of all implementations of
    /// an interface that reside in the current assembly.
    /// </summary>
    static class ImplementationLocator
    {
        /// <summary>
        /// Cache of the collection of possible types.
        /// </summary>
        static private List<TypeInfo> types;

        /// <summary>
        /// Locates and provides instance collections of requested interfaces.
        /// </summary>
        static ImplementationLocator()
        {
            var thisAssembly = Assembly.GetExecutingAssembly();
            types = thisAssembly.DefinedTypes.ToList();
        }

        /// <summary>
        /// Gets an instance of the first found implementation of the requested interface.
        /// </summary>
        /// <typeparam name="T">The interface type.</typeparam>
        static public T GetImplementation<T>() where T : class
        {
            return GetImplementations<T>().First();
        }

        /// <summary>
        /// Gets a collection of instances of every possible implementation of the requested interface.
        /// </summary>
        /// <typeparam name="T">The interface type.</typeparam>
        static public List<T> GetImplementations<T>() where T : class
        {
            var result = new List<T>();
            Type t = typeof(T);
            string interfaceNamespace = t.Namespace;
            string interfaceName = t.Name;
            var possibles = FindImplementations(interfaceNamespace, interfaceName);
            foreach (var c in possibles)
            {
                result.Add((T)(Activator.CreateInstance(Type.GetType(c.FullName))));
            }
            return result;
        }

        /// <summary>
        /// Gets a collection of TypeInfos for every possible implementation of the requested interface.
        /// </summary>
        /// <param name="interfaceNamespace"></param>
        /// <param name="interfaceName"></param>
        static private List<TypeInfo> FindImplementations(string interfaceNamespace, string interfaceName)
        {
            var result = new List<TypeInfo>();

            // Why's this not all one LINQ statement? For this method I prefer readability. Sorry.
            foreach (var typeInfo in types)
            {
                foreach (var implements in typeInfo.ImplementedInterfaces.Where(
                        a => a.IsInterface && a.Namespace == interfaceNamespace && a.Name == interfaceName))
                {
                    result.Add(typeInfo);
                }
            }
            return result;
        }
    }
}

Hope that helps someone.