Programmazione

Primary Constructor e Dependency Injection in C# 12: vantaggi, insidie e quando usarli

Dario Fadda Aprile 21, 2026

Con C# 12, Microsoft ha introdotto i primary constructors per tutte le classi e le struct — non solo per i record come in precedenza. Per chi lavora quotidianamente con ASP.NET Core e il pattern di dependency injection, questa feature merita attenzione: riduce il boilerplate in modo significativo, ma nasconde un'insidia che è bene conoscere prima di adottarla sistematicamente.

Il problema: il boilerplate del costruttore classico

Chi ha scritto servizi ASP.NET Core sa bene come appare il costruttore tradizionale con dependency injection:

public class OrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly ILogger<OrderService> _logger;
    private readonly IEventBus _eventBus;
    private readonly IValidator<Order> _validator;

    public OrderService(
        IOrderRepository orderRepository,
        ILogger<OrderService> logger,
        IEventBus eventBus,
        IValidator<Order> validator)
    {
        _orderRepository = orderRepository;
        _logger = logger;
        _eventBus = eventBus;
        _validator = validator;
    }
}

Per ogni dipendenza: una dichiarazione di campo, un parametro nel costruttore, un'assegnazione nel corpo. Con quattro dipendenze, sono già dodici righe di puro scaffolding. In un servizio con sei o sette dipendenze, il costruttore diventa il blocco di codice più lungo della classe — senza contenere logica.

La soluzione con primary constructors

Con i primary constructors di C# 12, lo stesso servizio si scrive così:

public class OrderService(
    IOrderRepository orderRepository,
    ILogger<OrderService> logger,
    IEventBus eventBus,
    IValidator<Order> validator)
{
    public async Task<Order?> GetOrderAsync(Guid id)
    {
        logger.LogInformation("Fetching order {OrderId}", id);
        return await orderRepository.GetByIdAsync(id);
    }

    public async Task PlaceOrderAsync(Order order)
    {
        await validator.ValidateAndThrowAsync(order);
        await orderRepository.SaveAsync(order);
        await eventBus.PublishAsync(new OrderPlacedEvent(order.Id));
    }
}

I parametri del primary constructor diventano direttamente disponibili in tutta la classe senza dichiarazioni esplicite di campo. Il risultato è codice più snello, con meno rumore visivo e il focus immediato sulla logica di business.

L'insidia della mutabilità: perché Milan Jovanović era inizialmente scettico

Il motivo di resistenza di molti sviluppatori esperti è legittimo: i parametri del primary constructor non sono campi readonly. Il compilatore non impedisce la riassegnazione accidentale:

public class OrderService(IOrderRepository orderRepository)
{
    public void SomeMethod()
    {
        orderRepository = null!;  // Compila senza warning
    }
}

Con i campi privati readonly tradizionali, questo codice causa un errore di compilazione. Con i primary constructors, il compilatore lo accetta silenziosamente. In un servizio DI dove le dipendenze non dovrebbero mai essere rimpiazzate a runtime, questo è un rischio concreto in team di grandi dimensioni.

Mitigazione: assegnazione esplicita a readonly field

Quando la garanzia di immutabilità è critica, si può assegnare esplicitamente il parametro a un campo readonly:

public class CriticalService(IRepository repository)
{
    private readonly IRepository _repository = repository;

    // Da qui in poi si usa _repository, mai repository direttamente
}

Questo reintroduce parte del boilerplate, ma mantiene la sintassi più compatta per la firma del costruttore e garantisce l'immutabilità a livello compilatore.

Quando usare i primary constructors

La valutazione pragmatica è che i primary constructors offrono il massimo vantaggio nelle classi di servizio DI tipiche di ASP.NET Core, dove i parametri vengono usati ma raramente riassegnati. In questi scenari, il rischio di mutabilità accidentale è basso e i benefici di leggibilità sono immediati.

Vale la pena usarli anche per entity e value object dove i parametri di costruzione diventano proprietà read-only:

public class Order(Guid customerId, Money total)
{
    public Guid Id { get; } = Guid.NewGuid();
    public Guid CustomerId { get; } = customerId;
    public Money Total { get; } = total;
    public DateTime CreatedAt { get; } = DateTime.UtcNow;
}

Quando evitarli

Tre scenari in cui i primary constructors non sono la scelta giusta:

  • Logica di validazione nel costruttore: se il costruttore deve eseguire guard clause o validazioni prima dell'assegnazione, il corpo esplicito del costruttore tradizionale è necessario.
  • Multiple signature di costruttore: i primary constructors supportano una sola firma. Con overload multipli (es. per serializzazione), la sintassi tradizionale è l'unica opzione.
  • Cinque o più dipendenze: se una classe richiede molte dipendenze, il problema non è la sintassi del costruttore ma il design della classe. Il segnale che suggerisce un refactoring verso interfacce più coese o il pattern Facade.

Conclusione: adottarli con consapevolezza

I primary constructors di C# 12 non sono una rivoluzione, ma un'evoluzione pragmatica del linguaggio. Per la maggior parte delle classi di servizio in ASP.NET Core, il tradeoff è favorevole: meno boilerplate, codice più leggibile, rischio di mutabilità basso nel contesto DI. L'importante è conoscere il comportamento del compilatore e scegliere consapevolmente quando la garanzia di readonly è effettivamente necessaria.

Come sempre con le feature di C#, il consiglio è adottarle dove migliorano la leggibilità del codice reale, non per seguire una moda sintattica.

Fonte originale: Why I Switched to Primary Constructors for DI in C# di Milan Jovanović.

💬 Unisciti alla discussione!


Questo è un blog del Fediverso: puoi trovare quindi questo articolo ovunque con @blog@spcnet.it e ogni commento/risposta apparirà qui sotto.

Se vuoi commentare su Primary Constructor e Dependency Injection in C# 12: vantaggi, insidie e quando usarli, utilizza la discussione sul Forum.
Condividi la tua esperienza, confrontati con altri professionisti e approfondisci i dettagli tecnici nel nostro 👉 forum community