Take advantage of the inversion of control pattern to loosely couple the components of your application and make them easier to test and maintain. Credit: Getty Images Both inversion of control and dependency injection enable you to break dependencies between the components in your application and make your application easier to teste and maintain. However, inversion of control and dependency injection are not the same — there are subtle differences between the two. In this article, we’ll examine the inversion of control pattern and understand how it differs from dependency injection with relevant code examples in C#. To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here. Create a console application project in Visual Studio First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio. Launch the Visual Studio IDE. Click on “Create new project.” In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed. Click Next. In the “Configure your new project” window shown next, specify the name and location for the new project. Click Create. This will create a new .NET Core console application project in Visual Studio 2019. We’ll use this project to explore inversion of control in the subsequent sections of this article. What is inversion of control? Inversion of control (IoC) is a design pattern in which the control flow of a program is inverted. You can take advantage of the inversion of control pattern to decouple the components of your application, swap dependency implementations, mock dependencies, and make your application modular and testable. Dependency injection is a subset of the inversion of control principle. In other words, dependency injection is just one way of implementing inversion of control. You can also implement inversion of control using events, delegates, template pattern, factory method, or service locator, for example. The inversion of control design pattern states that objects should not create objects on which they depend to perform some activity. Instead, they should get those objects from an outside service or a container. The idea is analogous to the Hollywood principle that says, “Don’t call us, we’ll call you.” As an example, instead of the application calling the methods in a framework, the framework would call the implementation that has been provided by the application. Inversion of control example in C# Assume that you are building an order processing application and you would like to implement logging. For the sake of simplicity, let’s assume that the log target is a text file. Select the console application project you just created in the Solution Explorer window and create two files, named ProductService.cs and FileLogger.cs. public class ProductService { private readonly FileLogger _fileLogger = new FileLogger(); public void Log(string message) { _fileLogger.Log(message); } } public class FileLogger { public void Log(string message) { Console.WriteLine("Inside Log method of FileLogger."); LogToFile(message); } private void LogToFile(string message) { Console.WriteLine("Method: LogToFile, Text: {0}", message); } } The implementation shown in the preceding code snippet is correct but there is a limitation. You are constrained to logging data to a text file only. You can’t in any way log data to other data sources or different log targets. An inflexible implementation of logging What if you wanted to log data to a database table? The existing implementation wouldn’t support this and you would be compelled to change the implementation. You could change the implementation of the FileLogger class, or you could create a new class, say, DatabaseLogger. public class DatabaseLogger { public void Log(string message) { Console.WriteLine("Inside Log method of DatabaseLogger."); LogToDatabase(message); } private void LogToDatabase(string message) { Console.WriteLine("Method: LogToDatabase, Text: {0}", message); } } You might even create an instance of the DatabaseLogger class inside the ProductService class as shown in the code snippet below. public class ProductService { private readonly FileLogger _fileLogger = new FileLogger(); private readonly DatabaseLogger _databaseLogger = new DatabaseLogger(); public void LogToFile(string message) { _fileLogger.Log(message); } public void LogToDatabase(string message) { _fileLogger.Log(message); } } However, although this would work, what if you needed to log your application’s data to EventLog? Your design is not flexible and you will be compelled to change the ProductService class every time you need to log to a new log target. This is not only cumbersome but also will make it extremely difficult for you to manage the ProductService class over time. Add flexibility with an interface The solution to this problem is to use an interface that the concrete logger classes would implement. The following code snippet shows an interface called ILogger. This interface would be implemented by the two concrete classes FileLogger and DatabaseLogger. public interface ILogger { void Log(string message); } The updated versions of the FileLogger and DatabaseLogger classes are given below. public class FileLogger : ILogger { public void Log(string message) { Console.WriteLine("Inside Log method of FileLogger."); LogToFile(message); } private void LogToFile(string message) { Console.WriteLine("Method: LogToFile, Text: {0}", message); } } public class DatabaseLogger : ILogger { public void Log(string message) { Console.WriteLine("Inside Log method of DatabaseLogger."); LogToDatabase(message); } private void LogToDatabase(string message) { Console.WriteLine("Method: LogToDatabase, Text: {0}", message); } } You can now use or change the concrete implementation of the ILogger interface whenever necessary. The following code snippet shows the ProductService class with an implementation of the Log method. public class ProductService { public void Log(string message) { ILogger logger = new FileLogger(); logger.Log(message); } } So far, so good. However, what if you would like to use the DatabaseLogger in lieu of the FileLogger in the Log method of the ProductService class? You could change the implementation of the Log method in the ProductService class to meet the requirement, but that doesn’t make the design flexible. Let’s now make the design more flexible by using inversion of control and dependency injection. Invert the control using dependency injection The following code snippet illustrates how you can take advantage of dependency injection to pass an instance of a concrete logger class using constructor injection. public class ProductService { private readonly ILogger _logger; public ProductService(ILogger logger) { _logger = logger; } public void Log(string message) { _logger.Log(message); } } Lastly, let’s see how we can pass an implementation of the ILogger interface to the ProductService class. The following code snippet shows how you can create an instance of the FileLogger class and use constructor injection to pass the dependency. static void Main(string[] args) { ILogger logger = new FileLogger(); ProductService productService = new ProductService(logger); productService.Log("Hello World!"); } In doing so, we have inverted the control. The ProductService class is no longer responsible for creating an instance of an implementation of the ILogger interface or even deciding which implementation of the ILogger interface should be used. Inversion of control and dependency injection help you with automatic instantiation and lifecycle management of your objects. ASP.NET Core includes a simple, built-in inversion of control container with a limited set of features. You can use this built-in IoC container if your needs are simple or use a third-party container if you would like to leverage additional features. You can read more about how to work with inversion of control and dependency injection in ASP.NET Core in my earlier post here. Related content feature What is Rust? Safe, fast, and easy software development Unlike most programming languages, Rust doesn't make you choose between speed, safety, and ease of use. Find out how Rust delivers better code with fewer compromises, and a few downsides to consider before learning Rust. By Serdar Yegulalp Nov 20, 2024 11 mins Rust Programming Languages Software Development how-to Kotlin for Java developers: Classes and coroutines Kotlin was designed to bring more flexibility and flow to programming in the JVM. Here's an in-depth look at how Kotlin makes working with classes and objects easier and introduces coroutines to modernize concurrency. By Matthew Tyson Nov 20, 2024 9 mins Java Kotlin Programming Languages analysis Azure AI Foundry tools for changes in AI applications Microsoft’s launch of Azure AI Foundry at Ignite 2024 signals a welcome shift from chatbots to agents and to using AI for business process automation. By Simon Bisson Nov 20, 2024 7 mins Microsoft Azure Generative AI Development Tools news Microsoft unveils imaging APIs for Windows Copilot Runtime Generative AI-backed APIs will allow developers to build image super resolution, image segmentation, object erase, and OCR capabilities into Windows applications. By Paul Krill Nov 19, 2024 2 mins Generative AI APIs Development Libraries and Frameworks Resources Videos