DEV Community

Theodore Karropoulos
Theodore Karropoulos

Posted on

How to use dependency injection and inversion of control in C#

What DI and IoC are

Dependency Injection (DI) and Inversion of Control (IoC) are software design patterns that are used to decouple the components of an application and make it more maintainable, scalable, and testable. Yeah right... but what does this actually means?

Think of it this way, when you are building a big project you might have different parts of the code that need to work together. But if each part is tightly connected to all the other parts, it can be a real headache to make changes or test the code. That's where DI and IoC come in!

DI stands for Dependency Injection. It's a way of making sure that one part of your code can get the information it needs from another part without having to know too much about it. It's like passing notes in class - one person writes the information down and passes it to another person, who can then use it without having to know anything about the person who wrote it.

IoC stands for Inversion of Control. It's a way of making sure that different parts of your code don't step on each other's toes. Instead of each part being in charge of its own little world, a central "controller" manages the flow of information and makes sure everything works together smoothly.

How to use DI in .net

Excellent, now you may ask, how do we utilize DI? Let's look at some code samples for registering services in di to better understand the concept.

Imagine we need to create a service to do something, I will leave it on your imagination what this magic method will do 😁

public interface IMyService
{
    void DoSomething();
}

public class MyService : IMyService
{
    public void DoSomething()
    {
        // Implementation of our magic service.
    }
}
Enter fullscreen mode Exit fullscreen mode

Now in order to register our service with the use of .NET build in DI container all we have to do is add our service to the IServiceCollection in the Startup, and specify its lifetime. Don't worry about the new words, we'll explain everything step by step and make it crystal clear. Let's get started!

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IMyService, MyService>();
    }
}
Enter fullscreen mode Exit fullscreen mode

And that was it, now MyService is available for usage in other parts of our application by injecting it into a constructor. For example:

public class MyController : Controller
{
    private readonly IMyService _myService;

    public MyController(IMyService myService)
    {
        _myService = myService;
    }

    public IActionResult DoSomething()
    {
        _myService.DoSomething();
        return Ok();
    }
}

Enter fullscreen mode Exit fullscreen mode

Now let's take a step back and clarify some of the new terms we just encountered.

What IServiceCollection is

IServiceCollection is an interface in the ASP.NET Core framework that acts as a collection of service descriptions. These service descriptions outline the services available in the application and the way they should be constructed and maintained.

In other words, the IServiceCollection is used to define the dependencies that the application requires and to specify the concrete implementations that should be used to satisfy those dependencies. When the application runs, the DI container uses the information stored in the IServiceCollection to create instances of the required services and to manage their lifetimes.

In our example, the IMyService interface defines a service contract, while the MyService class describes its actual implementation. To register the MyService with the IServiceCollection we use the AddTransient method on the IServiceCollection to define its lifetime and then pass the interface and its concrete implementation as types. In this manner, we indicate to the DI container that when an instance of IMyService is required, an instance of MyService will be produced and returned.

What lifetime is?

As previously stated, when we register a service in DI, we do so by passing its lifetime. But what exactly does that mean?

Lifetime refers to the length of time that a service instance created by the DI container will be used. There are three main lifetime options in ASP.NET Core DI:

  1. Transient, a new instance of the service is created each time it is requested. This is the default lifetime and is specified using the AddTransient method.
  2. Scoped, a new instance of the service is created for each unique request scope. This lifetime is specified using the AddScoped method.
  3. Singleton, a single instance of the service is created and shared for the entire application. This lifetime is specified using the AddSingleton method.

When to use each lifetime

Now that we've learned about the major dependency injection lifetimes, let's look at some examples of when to utilize each one.

Transient
This lifetime is appropriate for services that are stateless or have very short lifetimes. Examples of transient services include logging, configuration, and other utility services.

Scoped
This lifetime is appropriate for services that need to hold state that is specific to a single request, such as database transactions or user-specific information.

Singleton
This lifetime is appropriate for services that need to hold state that is shared across the entire application, such as a cache or a shared resource pool.

Potential implications of misused lifetimes

Although DI is a very powerful and useful pattern to use it is important to carefully consider the intended usage of each service and to choose the appropriate lifetime to ensure that the application behave as expected and to avoid potential implications.
Some potential implications of misusing lifetimes may be the following:

  1. Unexpected sharing of state, having multiple parts of the application share different instances of a service can result in the unintentional sharing of state, which can lead to complicated bug issues to track down.
  2. Concurrency issues, incorrect management of services can cause concurrency issues like race conditions, deadlocks, and other synchronization difficulties
  3. Inconsistent behavior, using different instances of the same service by various parts of the application can cause inconsistent behavior and make it challenging to debug and resolve issues.
  4. Dependency cycles, dependencies between services can lead to circular dependencies, making it a challenge to properly manage their lifetimes.

Summary

In summary Dependency Injection (DI) and Inversion of Control (IOC) are two super useful tools for any programmer to have in their toolkit. They're all about making our code more organized, easier to read, and less prone to problems.

With DI, we let something else worry about giving our code what it needs, so we can focus on writing the actual code.

IOC, on the other hand, is all about flipping control. Instead of us having to tell our code exactly what it needs, the code we're using takes care of providing it.

In C#, we can use the built-in DI container to keep track of our code's relationships and make sure everything is working together smoothly. And, we can manage the lifetimes of our code using different options like Transient, Scoped, or Singleton.

Just remember, managing the lifetimes correctly is key to avoiding problems like inconsistent behavior, state sharing, and circular dependencies.

Thank you for taking the time to read this article! I hope you found the information helpful and informative. If you have any further questions or comments, please feel free to reach out. Have a great day!

Latest comments (0)