Since C# 12, classes can have a primary constructor: parameters are declared next to the class name and are visible throughout the body. They are perfect for services with dependency injection.
Before and after
// Before
public class OrderService
{
private readonly IOrderRepository _repo;
private readonly ILogger<OrderService> _log;
public OrderService(IOrderRepository repo, ILogger<OrderService> log)
{
_repo = repo;
_log = log;
}
}
// With the primary constructor
public class OrderService(IOrderRepository repo, ILogger<OrderService> log)
{
public async Task<Order?> FindAsync(int id)
{
log.LogInformation("Looking up order {Id}", id);
return await repo.GetAsync(id);
}
}
The pitfall: parameters are not readonly
A primary constructor parameter can be modified inside the class. If you want the guarantee that it does not change, assign it to a readonly field:
public class Cache(TimeSpan duration)
{
private readonly TimeSpan _duration = duration;
}
Other things to watch
- Do not use the same parameter both directly and to initialise a field: the compiler warns because two copies of the state would be created.
- For types that represent data,
recordtypes remain the best choice: they generate properties, equality andToString. - If the constructor must validate parameters, validation in a field initialiser works, but for complex logic a classic constructor is clearer.
Comments (0)
No comments yet.