Simple Dependency Resolver

26,812

Solution 1

DI Containers are complex libraries. Building them takes years and maintaining them decades. But to demonstrate their working, you can write a simplistic implementations in just a few lines of code.

At its core a DI Container would typically wrap a dictionary with System.Type as its key and, the value would be some object that allows you to create new instances of that type. When you write a simplistic implementation System.Func<object> would do. Here is an example that contains several Register methods, both a generic and non-generic GetInstance method and allows Auto-Wiring:

public class Container
{
    private readonly Dictionary<Type, Func<object>> regs = new();

    public void Register<TService, TImpl>() where TImpl : TService =>
        regs.Add(typeof(TService), () => this.GetInstance(typeof(TImpl)));

    public void Register<TService>(Func<TService> factory) =>
        regs.Add(typeof(TService), () => factory());

    public void RegisterInstance<TService>(TService instance) =>
        regs.Add(typeof(TService), () => instance);

    public void RegisterSingleton<TService>(Func<TService> factory)
    {
        var lazy = new Lazy<TService>(factory);
        Register(() => lazy.Value);
    }

    public object GetInstance(Type type)
    {
        if (regs.TryGetValue(type, out Func<object> fac)) return fac();
        else if (!type.IsAbstract) return this.CreateInstance(type);
        throw new InvalidOperationException("No registration for " + type);
    }

    private object CreateInstance(Type implementationType)
    {
        var ctor = implementationType.GetConstructors().Single();
        var paramTypes = ctor.GetParameters().Select(p => p.ParameterType);
        var dependencies = paramTypes.Select(GetInstance).ToArray();
        return Activator.CreateInstance(implementationType, dependencies);
    }
}

You can use it as follows:

var container = new Container();

container.RegisterInstance<ILogger>(new FileLogger("c:\\logs\\log.txt"));

// SqlUserRepository depends on ILogger
container.Register<IUserRepository, SqlUserRepository>();

// HomeController depends on IUserRepository
// Concrete instances don't need to be resolved
container.GetInstance(typeof(HomeController));

WARNING: You should never use such naive and simplistic implementation as given above. It lacks many important features that mature DI libraries give you, yet gives no advantage over using Pure DI (i.e. hand wiring object graphs). You lose compile-time support, without getting anything back.

When your application is small, you should start with Pure DI and once your application and your DI configuration grow to the point that maintaining you Composition Root becomes cumbersome, you could consider switching to one of the established DI libraries.

Here are some of the features that this naive implementation lacks compared to the established libraries:

  • Auto-Registration: The ability to apply Convention over Configuration by registering a set of types with in single line, instead of having to register each type manually.
  • Interception: the ability to apply decorators or interceptors for a range of types
  • Generics: Mapping open-generic abstractions to open generic implementations
  • Integration: using the library with common application platforms (such as ASP.NET MVC, Web API, .NET Core, etc)
  • Lifetime Management: The ability to registering types with custom lifestyles (e.g. Scoped or Per Request).
  • Error handling: Detection of misconfiguration such as cyclic dependencies. This simplistic implementation throws a stack overflow exception.
  • Verification: Features or tools for verifying the correctness of the configuration (to compensate the loss of compile-time support) and diagnosing common configuration mistakes.
  • Performance: Building large object graphs will be slow using this simplistic implementation (e.g. causes a lot of GC pressure due to the amount of produced garbage).

These features and abilities allow you to keep your DI configuration maintainable when using a DI Container.

Solution 2

It's already a few years old, but Ayende once wrote a blog post about this:
Building an IoC container in 15 lines of code

But this is only the very simplest possible implementation.
Ayende himself stated in his next post that the existing IoC containers can do much more stuff than just returning class instances - and this is where it gets complicated.
As "Trust me - I'm a Doctor" already said in his comment: implementing a complete IoC container is everything but trivial.

Share:
26,812
Billa
Author by

Billa

Updated on February 11, 2022

Comments

  • Billa
    Billa over 2 years

    How do you create simple Dependency Resolver, with out using any built in or library such as Autofac, Ninject, etc.

    This was my interview question.

    I wrote this simple code and they said it does not look good. Its like very hard coded idea.

    public interface IRepository { }
    interface IDataProvider
    {
        List<string> GetData();
    }
    public class SQLDataProvider : IDataProvider
    {
        private readonly IRepository _repository { get; set; }
        public SQLDataProvider(IRepository repository)
        {
            _repository = repository;
        }
        public List<string> GetData()
        {
            return new List<string> { "" };
        }
    }
    public class MockDataProvider : IDataProvider
    {
        public List<string> GetData()
        {
            return new List<string> { "" };
        }
    }
    class Program
    {
     static void Main(string[] args)
     {
        string targetClass = "SQLDataProvider";
        //Here i need to supply IRepository instance too 
       IDataProvider dataProvider = 
       (IDataProvider)Activator.CreateInstance(typeof(IDataProvider), targetClass);
    
      }
    }
    

    What better code i do and supply other object instance for constructor parameter?

  • Mathiyazhagan
    Mathiyazhagan about 8 years
    Here container.GetInstance(typeof(HomeController)) returns Object type, Is it possible to TypeCast and return the HomeController type from GetInstance() method itself, i tried stackoverflow.com/questions/36750123/… in this code, but getting some sytax error
  • LastTribunal
    LastTribunal over 4 years
    Dear Steven, how would you modify your code to create a method that has the standard service locator signature..i.e. var instance = _container.GetInstance<IDoSomeStuff>()?
  • Steven
    Steven over 4 years
    You don't. You either use Pure DI or use a common DI library.
  • LastTribunal
    LastTribunal over 4 years
    @Steven can you elaborate on why you can't?
  • Steven
    Steven over 4 years
    The warning in my answer explains this.