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
NullReferenceExceptionat 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.