Dependency Injection: Constructor vs Manual Resolution
Key Considerations
| Category | Constructor Injection | Manual Resolution (IServiceProvider) |
|---|---|---|
| Performance | ✅ Optimal (resolve once) | ❌ Overhead per resolution |
| Memory Safety | ✅ Automatic disposal | ❌ Risk of leaks if using is omitted |
| Testability | ✅ Easy (inject mocks) | ❌ Harder (mock IServiceProvider) |
| Code Clarity | ✅ Explicit dependencies | ❌ Hidden dependencies |
| Use Case Fit | Default choice for most scenarios | Edge cases (e.g., dynamic/resolved-at-runtime) |
Example: Constructor Injection (Recommended)
public class EmailService
{
private readonly ILogger<EmailService> _logger;
public EmailService(ILogger<EmailService> logger)
{
_logger = logger;
}
public void SendEmail(string recipient)
{
_logger.LogInformation($"Sending email to {recipient}");
}
}
✅ Good For: - Web API Controllers - Background services - Unit testing with mock dependencies
❌ Not Ideal When: - You need runtime flexibility or conditional resolution of services
Example: Manual Resolution via IServiceProvider
public class DynamicServiceConsumer
{
private readonly IServiceProvider _provider;
public DynamicServiceConsumer(IServiceProvider provider)
{
_provider = provider;
}
public void Execute()
{
using var scope = _provider.CreateScope();
var emailService = scope.ServiceProvider.GetRequiredService<EmailService>();
emailService.SendEmail("test@example.com");
}
}
✅ Good For: - Factories - Plugins - Background tasks where service types vary
❌ Not Ideal When: - You want testable, clear, and maintainable code