Loading...

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


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


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