.NET

Dependency Injection – under the hood

There is a best practice in software development – Program to an interface, not an implementation. That is – use as much interfaces as you can and not concrete implementations. For example make your classes use ILogger and not FileLogger or ConsoleLogger.

To achieve that we use the Dependency Injection technique where you supply your classes with interfaces in the constructor or as properties and then have something else inject the dependencies after you have set up which concrete implementation to use for each interface, usually called dependency map. Still here?

There are a list of libraries to assist you with that in C# – > the most popular being Ninject, Castle Windsor, Unity etc. But let’s get our hands dirty and see how this happens under the hood. Consider the following entities:

    
    public interface ICharge
    {
        void Charge();
    }

    public class MasterCard : ICharge
    {
        public void Charge()
        {
            Console.WriteLine("payment was made with mastercard");
        }
    }

    public class Visa : ICharge
    {
        public void Charge()
        {
            Console.WriteLine("payment was made with visa");
        }
    }

Now we want to create a class Payment which can make payment with Visa or MC depending on the configuration that we setup:

  
    public class Payment
    {
        //constructor dependency
        public Payment(ICharge cc)
        {
            _cc = cc;
        }

        private ICharge _cc { get; set; }

        public void MakePayment()
        {
            _cc.Charge();
        }
    }

So that’s how we program to an interface (ICharge) without knowing the concrete implementation – Visa or MasterCard. We also need a class o resolve the dependencies for us, which we will call Resolver:

    
public class Resolver
{
    //store for dependency maps
    private Dictionary<Type, Type> dependencyMap = new Dictionary<Type, Type>();
        
    public void Register<T1, T2>;()
    {
        dependencyMap.Add(typeof(T1), typeof(T2));
    }
}

dependencyMap is the place where we setup which implementation we want to use for each interface. It is one more thing that we need and that is a Resolve method that will actually do the magic and give us classes with resolved dependencies to concrete implementations.

 public class Resolver
    {
        //store for dependency maps
        private Dictionary<Type, Type> dependencyMap = new Dictionary<Type, Type>();

        //this method calls the next one - we use this one publicly just because its syntax is cooler
        public T Resolve<T>()
        {
            return (T)Resolve(typeof(T));
        }

        private object Resolve(Type typeToResolve)
        {
            Type resolvedType = null;
            try
            {
                //check if we have a configured map
                resolvedType = dependencyMap[typeToResolve];
            }
            catch
            {
                throw new Exception(string.Format("Couldnt resolve {0}", typeToResolve.FullName));

            }

            //get the constructor and then the parameters
            var firstConstr = resolvedType.GetConstructors().First();
            var constrParameters = firstConstr.GetParameters();

            //in the case of parameterless constructor
            if (constrParameters.Count() == 0)
            {
                //return an instance of the resolved type
                return Activator.CreateInstance(resolvedType);
            }
            else
            {
                IList<object> parameters = new List<object>();
                foreach (var parameterToResolve in constrParameters)
                {
                    parameters.Add(Resolve(parameterToResolve.ParameterType));
                }
                return firstConstr.Invoke(parameters.ToArray());
            }
        }

        public void Register<T1, T2>()
        {
            dependencyMap.Add(typeof(T1), typeof(T2));
        }
    }  

The first Resolve method is the beautiful public version of the second one. The magic, however, happens in the second Resolve method. For those of you with reflection being their second nature, the example is pretty straightforward but let me explain for the others.First, we check if we have a configured dependency map for the type that we want to resolve and if not-throw an exception (the prerequisite for every dependency injection is a dependency map). Then we take the first constructor with reflection and check number of parameters – if parameters are zero, we have nothing to inject but instantiate the type with the Activator method. However, in our example Payment has a constructor with one parameter – ICharge. In this case (where the constructor has some parameters) we iterate through them and do the Resolve procedure recursively for each (Notice that Visa and MasterCard don’t have constructors, but a default constructor is added by the compiler). Then we store the resolved parameters in an array and finally we instantiate the initial class with all the resolved constructor parameters with the Invoke method. Let’s see how that works in our example:

        
static void Main(string[] args)
{
     //inversion of control container
     Resolver resolver = new Resolver();

     // register dependency map
     resolver.Register<Payment, Payment>();
     resolver.Register<ICharge, Visa>(); 

     // thats where the magic happens
     var payment = resolver.Resolve<Payment>();
     payment.MakePayment();
     Console.ReadLine();
}

We populate the dependency map with Payment -> Payment (with the dependency injection libraries you don’t have to map concrete types as here) and ICharge -> Visa. So when we say

var payment = resolver.Resolve<payment>();

the Resolve method returns a Payment instance with the _cc property set to Visa, so the console reads “payment was made with visa”. If we have mapped ICharge -> MasterCard we would have _cc property instantiated as MasterCard.This example looks a little burdensome to inject a single constructor parameter in a single class but imagine the following example

   
    public IHttpResult UserApiController(ILogger logger, IRepository repository, IUserManager userManager)    
    {        
        //some code    
    }

Now imagine that you have 30-40 api controllers and you have to use different loggers, repositories and user managers in different contexts. That’s where you start to feel the advantages of dependency injection. With 3 lines of code (setting up the dependency map in this case), you do the whole work of substituting implementations instead of going to every single controller and switch the parameters. It is also much more error free this way as you don’t repeat yourself.

Leave a Reply

Your email address will not be published. Required fields are marked *