C# Property Injection vs Constructor Injection

Dependency injection (DI) is a core design pattern in C# used to achieve Inversion of Control (IoC) between classes and their dependencies. This article examines the two primary ways to implement this pattern: constructor injection and property (setter) injection. You will learn the mechanics, code implementations, pros, cons, and standard use cases for each approach to help you choose the right pattern for your .NET applications.


Constructor Injection

Constructor injection is the industry standard in C# and the default approach supported natively by the Microsoft.Extensions.DependencyInjection container. Dependencies are provided to a class through its constructor parameters at instantiation.

Implementation

public interface IMessageService
{
    void Send(string message);
}

public class OrderProcessor
{
    private readonly IMessageService _messageService;

    // Dependency is injected via the constructor
    public OrderProcessor(IMessageService messageService)
    {
        _messageService = messageService ?? throw new ArgumentNullException(nameof(messageService));
    }

    public void ProcessOrder(int orderId)
    {
        // Business logic here
        _messageService.Send($"Order {orderId} processed successfully.");
    }
}

How to Register in .NET

var builder = WebApplication.CreateBuilder(args);

// Register dependencies
builder.Services.AddTransient<IMessageService, EmailService>();
builder.Services.AddTransient<OrderProcessor>();

Advantages

  • Immutability: Dependencies can be marked readonly, ensuring they are not modified after initialization.
  • Guaranteed State: The class cannot be created without its required dependencies, preventing NullReferenceException at runtime.
  • Native Support: Fully supported out of the box by standard .NET IoC containers.

Disadvantages

  • Can lead to bloated constructor parameter lists (constructor over-injection) if a class violates the Single Responsibility Principle.

Property Injection

Property injection (also called Setter Injection) supplies dependencies through public properties or setters after the object has already been instantiated. This pattern is commonly used for optional dependencies or when dealing with legacy frameworks where constructor control is limited.

Implementation

public class OrderProcessor
{
    // Optional dependency with a fallback default implementation
    public IMessageService MessageService { get; set; } = new NullMessageService();

    public void ProcessOrder(int orderId)
    {
        // Executes with injected service or fallback
        MessageService.Send($"Order {orderId} processed.");
    }
}

How to Perform Property Injection

The default Microsoft.Extensions.DependencyInjection container does not automatically populate properties. You must manually resolve and assign them, or use third-party containers such as Autofac.

Manual Resolution Example:

var processor = new OrderProcessor();
processor.MessageService = serviceProvider.GetRequiredService<IMessageService>();

Autofac Example:

var builder = new ContainerBuilder();
builder.RegisterType<EmailService>().As<IMessageService>();
builder.RegisterType<OrderProcessor>().PropertiesAutowired();

Advantages

  • Flexibility: Allows dependencies to be changed or assigned dynamically at any point in the object’s lifecycle.
  • Optional Dependencies: Ideal when a service has a sensible default behavior and an external implementation is optional.

Disadvantages

  • Hidden Dependencies: It is not obvious from the constructor which dependencies the class requires to function.
  • Mutability: Properties can be overwritten at any time, increasing the risk of unexpected side effects.
  • Requires Extra Configuration: Not automatically handled by the default .NET dependency injection container.

Key Comparison

Feature Constructor Injection Property Injection
Dependency Nature Required / Mandatory Optional / Configurable
Immutability Yes (readonly fields) No (requires accessible setters)
Creation Safety Always valid upon instantiation May be in an uninitialized state
.NET Core Support Built-in by default Requires third-party container or manual code

Recommendation

Always default to constructor injection in C# applications. It ensures your classes remain in a valid, immutable state and works seamlessly with the built-in .NET IoC container. Reserve property injection for scenarios involving optional configuration overrides, default fallbacks, or integration with frameworks that restrict constructor usage.