This post is about creating my own dependency injection system.
Dependency injection allows us to inject dependencies at run-time instead of compile-time. I’m going to start with the simple version, which I’ll describe using the following requirements:
- I want to define a binding between an interface and a class using generics. Obviously the class must implement the interface.
- This means that all dependencies must be known at compile-time. So for now I’m not going to include any run-time loaded types based on string values, or any other similar methods.
- I want to use fluent interfaces to define the binding.
- I want to be able to resolve an instance of an object by specifying the interface type.
Going from the list above I could come up with the following way to define a binding and to get an instance of an class:
In the text below, I will reference to the ITestObject type as the interface-type and the TestObject type as the class-type.
All right, lets make it happen. First we need a Factory class.
We are also in need of an interface to define a binding rule. We’ll worry about the implementation later.
And presto, now we can define binding rules in the factory. Let’s create a unittest. We need a dummy interface and class, and a unittest class.
Now we can run our unittest, and obviously it fails.
We need to create a binding rule class, and finish the factory class. To do that, we are also going to need another class, a class which will actually create an instance of an object. The reason for this is the following: When the Factory.Bind<TInterface>() method is called, an instance of the binding rule class is created. But the IBindingRule<TInterface>.To<TClass>() method specifies to which class-type the binding-rule should use. Therefore the binding rule cannot hold the class-type, and this is where the ‘instance creator’ comes into play. The instance creator class knows the interface-type, and the class-type, and therefore can return an object of the class-type.
I was struggling with the instance creator class at first, but once I figured out that the interface doesn’t know the class-type, and the class does know the class-type, things started to work out.
To reference the non-generic binding rules from the Factory a non-generic binding rule interface is necessary; the generic binding rule interface of course inherits this interface as well.
Lets create the binding rule class, the instance creator interface and class, and finish the factory class.
So what is exactly happening here?
Interface IInstanceCreator<TInterface> This interface defines an instance creator. Generics-wise it only knows the interface-type, since the binding rule must hold a reference to the instance creator. It has a single method, the Create() method, which is meant to return an instance of an object which implements the interface-type.
Class InstanceCreator<TInterface, TClass> This class implements the IInstanceCreator<TInterface> interface, and therefore has a Create() method. This method can simple create a new object of the class-type using the parameterless constructor.
Interface IBindingRule This interface defines a non-generic binding rule. This is necessary for the factory to reference a collection of binding rules. Of course it could be a list of objects as well, but using this interface it is somewhat strongly typed.
Interface IBindingRule<TInterface> This interface defines a generic binding rule. This interface exposes a single method to define the binding, specifying the class-type using generics.
Class BindingRule<TInterface> This class implements the IBindingRule<TInterface> interface. It is actually very simple. When the To<TClass> method is called, an instance of the InstanceCreator class is created. When the Create method is called, the instance creator is used to create an instance of the class-type.
Class Factory This class provides public access to the binding rules by exposing two static methods. One is the Bind<TInterface> method which can be used to create a binding rule. The binding rule is stored in a private dictionary. The other method is the GetInstance<TInterface> method in which a binding rule is retrieved from the dictionary, and the Create-method is used to get an instance of the object.
Now we have a unittest that passes. Excellent!
Using the factory we can define a binding rule in one assembly, and get an instance of a class that normally wouldn’t be accessible (without using reflection or some other method). The following diagram shows what I mean.
In the core assembly an interface is defined, and in that same assembly we want to use it. But the implementation is done in the implementing assembly which we can´t reference, since that assembly references the core assembly. This is possible using the factory.
Please note that the code shown above is not yet thread-safe.
In the next post I will introduce the concept of the binding scope. With the binding scope we can specify if and how previously created objects are re-used. For example: a singleton-scope.
The code can be downloaded here.