DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

Shuto Osawa
Shuto Osawa

Posted on

Learning Dependency Injection (IoC) Container in C#

Introduction

I used to see the word "DI Container" everywhere back when I was a beginner in C#. I am finally getting used to using dependency injection and it is a good to time to revisit DI Containers.

Dependency Injection

Dependency injection is a very important technique to achive loose-coupling in a software.
Instead of writing something below, we could remove tight coupling between classes.

public class Cat
{
    public void CheckFood()
    {
        Food catfood = new Food("cat food");
        Console.WriteLine("I am eating " + catfood.Name );
    }
}

public class Food
{
    public string Name {get;set;}

    public Food(string foodName)
    {
        this.Name = foodName;
    }
}
Enter fullscreen mode Exit fullscreen mode

In this case, the class Cat depends on Food.
We make the dependency better by injecting dependency from constructor.

public class Cat
{
    private Food food;
    public Cat(Food food)
    {
        this.food = food;
    }
    public void CheckFood()
    {
        Console.WriteLine("I am eating " + food.Name );
    }
}

public class Food
{
    public string Name {get;set;}

    public Food(string foodName)
    {
        this.Name = foodName;
    }
}
Enter fullscreen mode Exit fullscreen mode

The difference is that the food instance is not instantiated in the cat class, the food instance is injected through constructor.

Dependency Injection Container

We can simply keep using this dependency injection technique, however the instantiation of a class can be cumbersome. DI Container frameworks can help us with this work.

Simple implementation of interface through DI Container

Let's consider an IAnimal interface with dog and cat classes. They make different noises and we want to listen to them.

No DI Container

    public interface IAnimal
    {
        void noise();
    }

    public class Cat : IAnimal
    {
        public void noise()
        {
            Console.WriteLine("meow");
        }
    }

    public class Dog : IAnimal
    {
        public void noise()
        {
            Console.WriteLine("bark!");
        }
    }

Enter fullscreen mode Exit fullscreen mode

It is very easy to instantiate them even without the container.

IAnimal cat = new Cat();
IAnimal dog = new Dog();
cat.noise();
dog.noise();
Enter fullscreen mode Exit fullscreen mode

That's it!

With DI Container

container.RegisterType<IAnimal, Cat>();
container.RegisterType<IAnimal, Dog>();

IAnimal cat = container.Resolve<Cat>();
cat.noise();
IAnimal dog = container.Resolve<Dog>();
dog.noise();
Enter fullscreen mode Exit fullscreen mode

We can register the relationship between IAnimal and Cat. We can do the same for the dog class as well.
Since the relationship is registered to the container, we can create the object by calling Resolve method.

Somewhat complicated case

The example above is too easy and it does not really show the usefulness of the DI container.

Without DI Container

What if we have a object that depends on 9 containers and we want to use DI to implement it.

The constructor would look like this.

ParentObj parentObj = new ParentObj(new Obj1(new Obj4(), new Obj5(new Obj8())), new Obj2(new Obj6(new Obj8())), new Obj3(new Obj7(new Obj9())));
Enter fullscreen mode Exit fullscreen mode

This is fine if we only need to use this object once, but we may need to use something like this over and over.

With DI Container

With a DI container, we can make the instantiation part simple after properly wire them up.
The wiring process can be tedious, but we only need to do it once.

container.RegisterType<Obj8>();
//setup obj1
container.RegisterType<Obj4>();
container.RegisterType<Obj5>(new InjectionConstructor(container.Resolve<Obj8>()));
container.RegisterType<Obj1>(new InjectionConstructor(container.Resolve<Obj4>(), container.Resolve<Obj5>()));

//setup obj2
container.RegisterType<Obj6>(new InjectionConstructor(container.Resolve<Obj8>()));
container.RegisterType<Obj2>(new InjectionConstructor(container.Resolve<Obj6>()));

//setup obj3
container.RegisterType<Obj9>();
container.RegisterType<Obj7>(new InjectionConstructor(container.Resolve<Obj9>()));
container.RegisterType<Obj3>(new InjectionConstructor(container.Resolve<Obj7>()));

container.RegisterType<ParentObj>(new InjectionConstructor(container.Resolve<Obj1>(), container.Resolve<Obj2>(), container.Resolve<Obj3>()));
Enter fullscreen mode Exit fullscreen mode

Now we can generate the parent object.

ParentObj containerParentObj = container.Resolve<ParentObj>();
Enter fullscreen mode Exit fullscreen mode

I have used simple objects in this example, but in reality each object can be complicated and this could greatly reduce the pain of low readablity.

Where should I put the container

I had some questions about placing the container. I understood that the container can perform a lot of work for us, once all the classes are wired properly. Once we have the container, do we need to pass the containers around??
When we pass the container around classes then it becomes the service locater pattern and it is often seen as an anti-pattern.
The container should never leave the composition root and all the instances should come from the composition root.

Conclusion

I have looked into the basic usage of DI Containers in C#. I did not go over the details of how we can use the DI Container, such as checking instance life cycles. However, it is a good starting point to understand the DI container itself.

Top comments (0)

CSS margins

Stop by this week's meme thread!