Dependency Injection in C#: was es ist und warum man es braucht
Dependency Injection (DI) ist eines der wichtigsten Entwurfsmuster, das es ermöglicht, lose gekoppelte und leicht testbare Anwendungen in C# zu erstellen. Anstatt dass eine Klasse ihre Abhängigkeiten selbst erstellt (z. B. Objekte für die Datenbankarbeit oder Protokollierung), werden diese Abhängigkeiten von außen übergeben (injiziert). Dies realisiert das Prinzip Inversion of Control (IoC) – die Kontrolle wird an einen externen Container übergeben.
Im .NET-Ökosystem ist DI seit ASP.NET Core zum De-facto-Standard geworden. Der integrierte Abhängigkeitscontainer (über Microsoft.Extensions.DependencyInjection) ermöglicht es, Abhängigkeiten einfach zu registrieren und aufzulösen. In diesem Artikel werden wir die Theorie, praktische Beispiele und bewährte Methoden zur Verwendung von DI in C# behandeln.
Grundprinzipien der Dependency Injection
DI basiert auf drei grundlegenden Methoden der Abhängigkeitsinjektion. Betrachten wir jede davon.
1. Injektion über den Konstruktor
Die gebräuchlichste und bevorzugte Methode. Abhängigkeiten werden über die Parameter des Klassenkonstruktors übergeben.
public interface ILogger{ void Log(string message);}
public class ConsoleLogger : ILogger{ public void Log(string message) => Console.WriteLine(message);}
public class OrderService{ private readonly ILogger _logger;
// Abhängigkeitsinjektion über den Konstruktor public OrderService(ILogger logger) { _logger = logger; }
public void ProcessOrder(int orderId) { _logger.Log($"Verarbeitung der Bestellung {orderId}"); // Geschäftslogik }}2. Injektion über eine Eigenschaft
Die Abhängigkeit wird über eine öffentliche Eigenschaft gesetzt. Weniger bevorzugt, da es die Abhängigkeit optional macht und zu Laufzeitfehlern führen kann.
public class OrderService{ public ILogger Logger { get; set; }
public void ProcessOrder(int orderId) { if (Logger == null) throw new InvalidOperationException("Logger wurde nicht gesetzt"); Logger.Log($"Verarbeitung der Bestellung {orderId}"); }}3. Injektion über eine Methode
Die Abhängigkeit wird als Methodenparameter übergeben. Wird selten verwendet, normalerweise für temporäre Abhängigkeiten.
public class OrderService{ public void ProcessOrder(int orderId, ILogger logger) { logger.Log($"Verarbeitung der Bestellung {orderId}"); }}Registrierung von Abhängigkeiten im .NET-Container
Im modernen .NET (ASP.NET Core, Console, Worker Services) wird der integrierte DI-Container verwendet. Die Registrierung erfolgt in der Methode ConfigureServices oder beim Erstellen einer ServiceCollection.
Es gibt drei Lebensdauern (lifetimes) für registrierte Dienste:
- Transient – Bei jeder Anforderung wird eine neue Instanz erstellt. Geeignet für leichte, kurzlebige Dienste.
- Scoped – Eine Instanz pro HTTP-Anfrage (oder Scope). Wird für Entity Framework DbContext verwendet.
- Singleton – Eine Instanz für die gesamte Anwendung. Geeignet für Caches, Einstellungen, Logger.
// Beispiel für ASP.NET Corepublic class Startup{ public void ConfigureServices(IServiceCollection services) { // Registrierung mit Schnittstelle services.AddTransient<ILogger, ConsoleLogger>(); services.AddScoped<IOrderRepository, OrderRepository>(); services.AddSingleton<IConfiguration, ConfigurationManager>();
// Registrierung ohne Schnittstelle (konkrete Klasse) services.AddTransient<OrderService>(); }}
// Verwendung im Controllerpublic class OrderController : ControllerBase{ private readonly OrderService _orderService;
public OrderController(OrderServi