Iniezione tramite costruttore e interfacce
Applichi l'iniezione tramite costruttore con una progettazione basata sulle interfacce, così da disaccoppiare i componenti e abilitare i test unitari.
Iniezione tramite costruttore e interfacce è una lezione C# Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento C# Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso C# Academy include 4 lezioni in totale.
Nozioni di base sull'iniezione tramite costruttore
L'iniezione tramite costruttore è il pattern DI più comune. Una classe dichiara le proprie dipendenze come parametri del costruttore e il container le fornisce al momento dell'istanziazione.
In questo modo le dipendenze sono esplicite, obbligatorie e visibili.
Definizione dell'interfaccia
Inizi con un'interfaccia che definisce il contratto. Questo consente di sostituire implementazioni diverse, reali, mock o in memoria, senza modificare il codice dipendente.
public interface IProductRepository
{
Task<Product?> GetByIdAsync(int id);
Task<IEnumerable<Product>> GetAllAsync();
Task AddAsync(Product product);
}Implementazione dell'interfaccia
L'implementazione concreta gestisce il lavoro effettivo, come le chiamate al database e le richieste HTTP. Il consumer non deve mai sapere quale implementazione riceve.
public class SqlProductRepository : IProductRepository
{
private readonly AppDbContext _db;
public SqlProductRepository(AppDbContext db) => _db = db;
public async Task<Product?> GetByIdAsync(int id)
=> await _db.Products.FindAsync(id);
public async Task<IEnumerable<Product>> GetAllAsync()
=> await _db.Products.ToListAsync();
public async Task AddAsync(Product product)
{
_db.Products.Add(product);
await _db.SaveChangesAsync();
}
}Utilizzo tramite il costruttore
Il livello dei servizi riceve IProductRepository come parametro del costruttore. Non conosce mai SqlProductRepository: conosce solo il contratto dell'interfaccia.
public class ProductService
{
private readonly IProductRepository _repo;
private readonly ILogger<ProductService> _logger;
public ProductService(
IProductRepository repo,
ILogger<ProductService> logger)
{
_repo = repo;
_logger = logger;
}
public async Task<Product?> GetProductAsync(int id)
{
_logger.LogInformation("Fetching product {Id}", id);
return await _repo.GetByIdAsync(id);
}
}Registrazione della catena
Registri ogni servizio della catena. Il container risolve automaticamente l'intero grafo delle dipendenze: ProductService → IProductRepository → AppDbContext.
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlite("Data Source=app.db"));
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();
builder.Services.AddScoped<ProductService>();Test con un mock
Poiché le dipendenze vengono inserite tramite interfacce, i test unitari possono utilizzare un mock o un fake senza accedere al database. Questo è uno dei principali vantaggi della DI.
// Using NSubstitute or Moq in a unit test
var mockRepo = Substitute.For<IProductRepository>();
mockRepo.GetByIdAsync(1).Returns(new Product { Id = 1, Name = "Widget" });
var logger = Substitute.For<ILogger<ProductService>>();
var svc = new ProductService(mockRepo, logger);
var result = await svc.GetProductAsync(1);
Assert.Equal("Widget", result?.Name);Più parametri del costruttore
Una classe può avere numerosi parametri del costruttore. Il container li risolve tutti, purché ciascuno sia registrato. Mantenga i costruttori focalizzati: un numero eccessivo di parametri è un segnale che la classe ha troppe responsabilità.
public class OrderService
{
public OrderService(
IOrderRepository orders,
IProductRepository products,
IEmailSender email,
ILogger<OrderService> logger)
{
// all injected by DI container
}
}Evitare l'anti-pattern Service Locator
NON inserisca IServiceProvider e chiami GetService all'interno dei metodi di business. Questo nasconde le dipendenze e rende difficili i test. Preferisca l'iniezione tramite costruttore per ottenere codice esplicito e verificabile.
// BAD
public void Process(IServiceProvider sp)
{
var repo = sp.GetService<IProductRepository>(); // hidden dep!
}
// GOOD
public class Processor
{
private readonly IProductRepository _repo;
public Processor(IProductRepository repo) => _repo = repo;
}Sintassi del costruttore primario (C# 12)
C# 12 introduce i costruttori primari per tutti i tipi di classe, consentendo di dichiarare i parametri direttamente nella dichiarazione della classe e rendendo più conciso il codice DI.
// C# 12 primary constructor
public class ProductService(
IProductRepository repo,
ILogger<ProductService> logger)
{
public async Task<Product?> GetAsync(int id)
{
logger.LogInformation("Getting {Id}", id);
return await repo.GetByIdAsync(id);
}
}Sostituzione delle implementazioni
Un vantaggio fondamentale della DI basata sulle interfacce è la possibilità di sostituire un'implementazione con una sola riga, senza modificare il codice che la utilizza. È utile per cambiare database, provider di posta elettronica o feature flag.
// Switch from SQL to in-memory for integration tests
if (environment.IsEnvironment("Test"))
builder.Services.AddScoped<IProductRepository, InMemoryProductRepository>();
else
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();Caso reale: controller con DI
I controller ASP.NET Core utilizzano nativamente l'iniezione tramite costruttore. Il framework risolve tutti i parametri e li passa al controller.
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly ProductService _svc;
public ProductsController(ProductService svc) => _svc = svc;
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var product = await _svc.GetProductAsync(id);
return product is null ? NotFound() : Ok(product);
}
}Verifica rapida
Qual è il principale vantaggio di programmare verso le interfacce quando si utilizza la DI?
Riepilogo: iniezione tramite costruttore e interfacce
Punti chiave:
- Dichiari le dipendenze come parametri del costruttore: sarà il container a fornirle
- Definisca le interfacce per i contratti dei servizi; inserisca l'interfaccia, non il tipo concreto
- Le interfacce facilitano l'utilizzo di mock nei test unitari e la sostituzione delle implementazioni
- I costruttori primari di C# 12 riducono il codice ripetitivo nelle classi che usano molto la DI
- Eviti l'anti-pattern Service Locator e mantenga esplicite le dipendenze
Domande Frequenti
La lezione «Iniezione tramite costruttore e interfacce» è gratuita?
Sì — il testo completo di «Iniezione tramite costruttore e interfacce» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso C# Academy, passa a CoddyKit PRO. Il corso C# Academy include 4 lezioni in totale.
Cosa imparerò in «Iniezione tramite costruttore e interfacce»?
Applichi l'iniezione tramite costruttore con una progettazione basata sulle interfacce, così da disaccoppiare i componenti e abilitare i test unitari. Eserciti C# Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare C# Academy?
Non è richiesta alcuna esperienza precedente. C# Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.
Quanto tempo richiede la lezione «Iniezione tramite costruttore e interfacce»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione C# Academy?
Sì. Ogni lezione C# Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Fondamenti dei container DI
- Durata dei servizi: Transient, Scoped, Singleton
- Iniezione tramite costruttore e interfacce
- Pattern Factory e Options