Building IoC container cSharp example
Feb 5, 2013 00:00 · 783 words · 4 minute read
What is IoC?
IoC is referring to inversion of control which means, instead of asking our supper class match themselves with subclasses , ask subclasses to adopt themselves with super class by implementing their interface.
For instance forcing all electrical devices to use the apple port to charge themselves rather than building different charger for devices.
Some definition
DIP: Dependency Inversion principal refers to the idea of inversion of control
IoC :Refer to the pattern that we use to inverting the control
DI: dependency Injection refers to implementation of the pattern but not necessarily could guide us to achieve our goal.
What is IoC container ?
It is a framework to do configure the dependencies automatically and inject them into the subclass tree.
Example
What we have?
We have a printer class that accept the printer type and do the printing according to the passed type.
print class
public class Print {
private readonly ITypeOfPrinter _typeOfPrinter;
public Print(ITypeOfPrinter typeOfPrinter) {
_typeOfPrinter = typeOfPrinter;
}
public string print() {
return _typeOfPrinter.Printing();
}
}
The interface of printer type is simple and have only one method to do the printing
interface
public interface ITypeOfPrinter {
string Printing ();
}
There are currently two types of printer , colourful and black&White.
colorfull printer
public class ColorfullPrinter : ITypeOfPrinter {
public string Printing() {
NumberOfPrints++;
return string.Format("it is colorfull printing {0}",NumberOfPrints);
}
public int NumberOfPrints { get; set; }
}
Which are pretty much similar in their implementation
Black&White Printer
public class BlackAndWhitePrinter : ITypeOfPrinter {
public string Printing() {
NumberOfPrints++;
return string.Format("it is Black and white printing {0}",NumberOfPrints);
}
public int NumberOfPrints { get; set; }
}
Using the class normally
Normally to calling this kind of class we have to pass the dependency into it and then call the desired method on it .
For instance if we wish to have colourful printing we passing the colourful printer like below.
using class normally
[Test]
public void Given_ColorfulPrinterType_When_Print_Then_ItShouldBeColorful() {
var print=new Print(new ColorfullPrinter());
string printedText = print.print();
Assert.IsTrue(printedText.Contains("colorfull"));
}
Using Resolver
To make our method more single responsible we can pass the responsibility of making dependency into another class and ask it to decide what type of printer we need in each case.
Resolver class
public ITypeOfPrinter ResolvePrinterType() {
ITypeOfPrinter typeOfPrinter;
var random = new Random();
if (random.Next(2) == 1) {
typeOfPrinter = new ColorfullPrinter();
} else {
typeOfPrinter = new BlackAndWhitePrinter();
}
return typeOfPrinter;
}
Using IoC container
If our class constructor has too many dependencies or require variable building we can make an IoC container to help us to register our dependencies easier.
In this case we can have Resolve method that accept generic type and return the appropriate generic type.
Resolver method
public T Resolve<T>() {
return (T)Resolve(typeof(T));
}
It is looking into our dictionary and find the desired type of it.
Resolve object
private object Resolve(Type type) {
Type resolveType=null;
try {
resolveType = dictionaryType[type];
} catch {
throw new Exception(string.Format("The requested type {0} is not in registered dictionary",type));
}
ConstructorInfo firstConstructorInfo = resolveType.GetConstructors().First();
ParameterInfo[] constructorParameters = firstConstructorInfo.GetParameters();
if(!constructorParameters.Any()) {
return Activator.CreateInstance(resolveType);
}
var resolveParameterList= constructorParameters.Select(parameterToResolve => Resolve(parameterToResolve.ParameterType)).ToList();
return firstConstructorInfo.Invoke(resolveParameterList.ToArray());
}
We seek for the first constructor in this example and try to retrieve all of the input variable of it. If the constructor does not have any input variable it create an instance of it. Otherwise go to all the input variable and make an instance of it and add it to the dictionary.Finally we can invoke the constructor with its input variables and return it.
Registration
The last step is to register our dependencies. We have to map the appropriate called type to the output type by adding them to our dictionary.
Registering
public void Register<TFrom, TTo>() {
dictionaryType.Add(typeof(TFrom),typeof(TTo));
}
For instance we should map ITypeOfPrinter to Colorfull printer class.
Calling it
In order to call it we have to register the dependencies by mapping Print to Print and Interface to the desired concrete class.
calling the resolver
[Test]
public void Given_Nothing_When_Print_Then_ItShouldFindAppropriateType() {
var resolver = new Resolver();
resolver.Register<Print, Print>();
//resolver.Register<ITypeOfPrinter, ColorfullPrinter>();
resolver.Register<ITypeOfPrinter, BlackAndWhitePrinter>();
var resolvedPrinter = resolver.Resolve<Print>();
string printedText = resolvedPrinter.print();
Assert.Pass(printedText);
}
And ask the resolver to resolve it and return instances of it to us.
What will be happening?
By registering type we add those key and value to our dictionary, therefore when it tries to resolve it will pass Print class to the method. Our constructor has one input and it will recursively pass it to the Resolve method to make the instance of it as well.By considering what is our registered type we can have colorfull or Black&White printing.
Download
Feel free to download the full source code of building IoC container from my github.