Dependency Injection Basics in C#
Learn the basics of Dependency Injection in C#, including managing dependencies, loose coupling, and improving testability.
In software development, Dependency Injection (DI) is an important design pattern used to manage dependencies. With DI, classes receive the objects they need from the outside instead of creating them internally. This makes code more flexible, testable, and maintainable. In the C# and .NET Core ecosystem, DI is supported out of the box.
What is a Dependency?
A class depends on another class when it requires its functionality.
For example, an OrderService may need an ILogger instance when creating an order.
If OrderService directly uses new Logger(), it becomes tightly coupled to the Logger class.
public class OrderService
{
private readonly Logger _logger = new Logger();
public void CreateOrder(string product)
{
_logger.Log($"Order created: {product}");
}
}
public class Logger
{
public void Log(string message) => Console.WriteLine(message);
}
In this approach, the Logger cannot be replaced. If you want to use a different logging system,
you must modify the OrderService code.
This is where Dependency Injection comes into play.
Solution with Dependency Injection
When DI is used, dependencies are abstracted and provided from the outside (via constructor or method parameter).
This way, OrderService depends on the ILogger interface, and the external world decides which logger to use.
public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message) => Console.WriteLine("[Console] " + message);
}
public class FileLogger : ILogger
{
public void Log(string message) =>
System.IO.File.AppendAllText("log.txt", message + "\n");
}
public class OrderService
{
private readonly ILogger _logger;
// Constructor Injection
public OrderService(ILogger logger)
{
_logger = logger;
}
public void CreateOrder(string product)
{
_logger.Log($"Order created: {product}");
}
}
class Program
{
static void Main()
{
// Different loggers can be selected
ILogger logger = new ConsoleLogger();
var service = new OrderService(logger);
service.CreateOrder("Laptop");
}
}
In this example, OrderService is no longer tied to a specific Logger class; it only depends on the ILogger interface.
Therefore, ConsoleLogger or FileLogger can be easily swapped.
Types of Dependency Injection
- Constructor Injection: Dependencies are provided through the constructor (most common method).
- Property Injection: Dependencies are assigned through public properties.
- Method Injection: Dependencies are passed via method parameters.
Using DI in .NET Core
In .NET Core applications, a built-in DI container is available.
Services are registered in Program.cs or Startup.cs, and they are automatically injected into classes where needed.
var builder = WebApplication.CreateBuilder(args);
// Register services
builder.Services.AddScoped<ILogger, ConsoleLogger>();
builder.Services.AddScoped<OrderService>();
var app = builder.Build();
app.MapGet("/order", (OrderService service) =>
{
service.CreateOrder("Phone");
return "Order processed.";
});
app.Run();
Here, the DI container automatically creates and injects a ConsoleLogger instance for OrderService.
Advantages
- Loose Coupling: Classes depend on interfaces rather than specific implementations.
- Testability: Unit testing is easier with mock or fake objects.
- Flexibility: Different implementations can be swapped easily.
- Maintainability: Dependencies are managed centrally, improving readability and maintainability.
TL;DR
- Dependency Injection: Classes receive the objects they need from outside.
- Constructor Injection: The most common approach; dependencies are passed via constructors.
- .NET Core: Provides a built-in DI container.
- Benefits: Enables more flexible, testable, and maintainable architecture.
Related Articles
Class, Object, Property and Methods in C#
Learn how classes, objects, properties, and methods work in C# and form the core building blocks of object-oriented programming.
Encapsulation, Inheritance, and Polymorphism in C#
Learn encapsulation, inheritance, and polymorphism in C# with clear examples to understand core OOP principles and real use cases.
Interfaces and Abstract Classes in C#
Learn interfaces and abstract classes in C#, their differences, and when to use each approach to design clean and extensible code.
Methods and Parameter Usage in C#
Learn how to define methods and use parameters in C#, including value and reference parameters, optional parameters, and examples.
Namespaces and Assemblies in C#
Learn how namespaces and assemblies work in C# to organize code, manage dependencies, and structure projects effectively.
SOLID Principles with C#
Applying SOLID principles in C# with examples: building flexible, maintainable, and testable code.