If you work with object-oriented programming, you’ve probably heard the term dependency injection at least a few times. But what is dependency injection?
Dependency injection is a technique of passing the dependencies (other objects) to the class as constructor parameters, rather than allowing the class to create its own dependencies.
It’s a technique used to reduce the necessity of creating objects inside the class and minimize tight coupling.
Dependency injection has the following benefits:
- Enables clean code
- Enables unit testing
- Encourages decoupling
- Improves code maintainability
- Centralizes code configuration
Dependency Injection is one of the most important tools in your arsenal for making any C# code testable, easy to read, and just plain better.
What is Dependency Injection?
Dependency injection (or dependency inversion) is a design pattern that reduces some of the complexity of creating software systems. It describes how an object can get its dependencies.
It does so through an inversion of control, which is a fancy name for a simple idea. It simply means that instead of a class instantiating its own dependencies, there is a DI container that creates the instance of a class by providing the dependencies or components. It means that classes depend on abstractions (interfaces) instead of concrete classes.
It also makes the class more testable because you can test the class in isolation from external dependencies.
Dependency injection is used to reduce the necessity of creating objects inside the class
When a class is completely dependent on another class, it typically creates an instance of that class inside the class itself.
This can lead to several problems:
- The class becomes tightly coupled to the outer class that it is dependent on.
- The class is hard to test because the outer class is not easily testable.
- It becomes harder to make changes to the outer class because it is dependent on the inner class.
- It is harder to reuse the class because it is tightly coupled to the outer class.
Types of Dependency Injection
There are three types of Dependency injection:
- Constructor Injection – The constructor injection is the most common type of dependency injection. It is a technique of injecting a class’s dependencies through the constructor of that class. You pass every dependency as a constructor argument. If done right, you will be injecting interfaces of the dependencies instead of real classes. This is also known as interface injection.
- Property Injection – Property injection is a technique that involves passing a dependency the client class needs through the property of that class. The main advantage of property injection is that you don’t have to change the existing constructors of the class to add a new dependency. You can also pass this dependency in a lazy load way. This means that the concrete class is not initialized until you call the property of the dependent class. The alternative way to use this type of injection is to have a setter method. This is a method whose sole responsibility is to take the dependency and store it in a variable.
- Method Injection – Method injection is a powerful yet very commonly overlooked technique. It can be used to inject behavior into an object at run-time without modifying the object’s code. This allows for a more flexible system. With this type of injection, you pass the dependency as a method argument.
5 benefits of using dependency injection in your C# code
You may not know it, but dependency injection is a big deal in the programming world. Here are five reasons dependency injection is a must for any C# programmer.
1. With Dependency injection, you can have clean code
One of the most common issues developers have with their code is that it is cluttered with dependencies. For example, a common pattern is to create a global variable that stores a reference to the class or service you’re using. That works well for a while. But, if you have two or more instances of that class or service in your code and you have to keep track of which one you’re manipulating, then you are in trouble. Dependency injection solves this problem by decoupling the thing that needs a dependency from the thing that provides it.
A key goal of software engineering is to produce code that is clean and easy to maintain. Code is clean when it is simple, readable, and understandable. This is not the case with code where dependencies are not injected, and the code is coupled with dependencies.
When classes create dependencies they need on their own or call singletons, the classes become messier, longer and it’s harder to reuse pieces of code in other places. This leads to a lot of duplicated code.
With dependency injection, you inject dependencies into a dependent object. This means there are fewer static classes that are called from different parts of the system.
2. With Dependency injection, you can write unit tests
One of the best ways of ensuring that your program doesn’t blow up on you is with unit tests. After all, if you’re writing a unit test for an object, the developer who comes after you has the responsibility to ensure that this test never fails.
If you’re not testing your code, you’re doing it wrong. But sometimes, testing can be difficult and messy. The problem is that dependencies can be hard to mock. If you have a dependency on a database, you can’t just mock that database.
Enter dependency injection pattern.
Dependency injection can be a huge help in making unit testing work for you.
When you inject the interfaces of dependencies, you can provide a test double (a mock object or a stub object) for an injected interface. This means you control how the injected dependency behaves:
- You can return actual values to the class under test.
- You can return null or throw an exception.
- You can check that your class calls another method with the correct parameters.
3. Dependency injection encourages decoupling
You can use dependency injection to decouple concrete classes one from another. You can achieve that by injecting interfaces instead of concrete classes. This creates loosely coupled code.
With this approach, your class doesn’t know which concrete implementation of a dependency it uses. Nor does it not care. The only thing it cares for is that the dependency follows the contract that the interface has established.
When you have loose coupling between classes, it’s easier to maintain such an application. And whenever the component’s dependencies change, that change does not affect the instance of your class.
4. Dependency injection improves code maintainability
It’s no secret that software development is hard. Code is complex and constantly changing. Developers are always looking for ways to make development easier. One technique to improve code maintainability is dependency injection.
Let’s go through one example to see the maintainability benefit.
You have a website application that uses MySQL as a database. Then someone makes a decision that the website should use the MS SQL database instead. If your database layer is isolated behind an interface, then no problem. You just create a new implementation of the database layer and you are done. However, if your SQL code is everywhere throughout the app, you will have difficulty explaining why you need so much time to change one database for another.
Code maintainability is important because it impacts the amount of time and money required to modify the code.
5. Dependency injection centralizes code configuration
A common issue with dependency injection, or DI, is that it can be a lot of work to set up. You often have to create interfaces for each object, construct the objects, and wire them up to each other. Fortunately, there’s an easier way.
You can use an Inversion of Control, or IoC, container. With an IoC container, you simply specify the types of objects you need and tell it how to construct them. It also helps you to wire up objects to each other.
With the use of IoC containers, your application will be composed on the fly. You can also centralize dependency injection container usage. This means that you can have a single class, or just a few of them, for all dependency configurations.
This means the next time you need to change a dependency you use throughout the application, there is a single place in your code to make that change.
There are a lot of IoC containers out there. I use Autofac.
Summary
Dependency injection (DI) is the most powerful way to manage how classes interact in any application. While the name sounds like something from Star Wars, the practice is ubiquitous and important in many domains and frameworks. Dependency injection works with objects and services to make them easier to create, more maintainable, and resilient to changes.
Dependency injection is more than a buzzword.
It’s a way of programming.
To write scalable and maintainable code, we need a way to break it up and isolate it. In this post, I have explained the benefits of DI and how it will help you write isolated code.