Was ist Dependency Injection (DI) in C# und warum wird es benötigt?
\n\nDependency Injection (Abhängigkeitsinjektion) ist ein Entwurfsmuster, das den Code flexibler, testbarer und wartbarer macht. Anstatt dass eine Klasse die Objekte, von denen sie abhängt, selbst erstellt, werden diese Objekte von außen "injiziert". Dies ist eines der Schlüsselprinzipien der modernen Entwicklung mit C# und .NET.
\n\nStellen Sie sich vor, Sie schreiben eine Anwendung zum Versenden von E-Mail-Benachrichtigungen. Wenn Ihre Klasse NotificationService direkt eine Instanz von SmtpClient erstellt, müssten Sie bei einem Wechsel des Mailservers oder der Notwendigkeit, eine andere Versandart (z. B. über eine API) zu verwenden, den Code umschreiben. DI löst dieses Problem.
Das Problem ohne Dependency Injection
\n\nBetrachten wir ein typisches Beispiel für schlechten Code, bei dem die Abhängigkeit innerhalb der Klasse erstellt wird:
\n\npublic class EmailService\n{\n private SmtpClient _smtpClient;\n\n public EmailService()\n {\n // Feste Bindung an eine bestimmte Implementierung\n _smtpClient = new SmtpClient("smtp.gmail.com", 587);\n }\n\n public void SendEmail(string to, string subject, string body)\n {\n // Logik zum Senden\n }\n}\n\npublic class NotificationService\n{\n private EmailService _emailService;\n\n public NotificationService()\n {\n _emailService = new EmailService(); // Feste Kopplung\n }\n\n public void NotifyUser(string email)\n {\n _emailService.SendEmail(email, "Benachrichtigung", "Hallo!");\n }\n}\n\nNachteile dieses Ansatzes:
\n- \n
- Hohe Kopplung (tight coupling): Die Klassen
NotificationServiceundEmailServicesind fest aneinander gebunden. \n - Schwierigkeit beim Testen: Es ist unmöglich,
EmailServicebeim Komponententest durch ein Mock-Objekt zu ersetzen. \n - Schwierigkeit bei der Erweiterung: Wenn SMS-Benachrichtigungen hinzugefügt werden sollen, muss die Logik innerhalb von
NotificationServicegeändert werden. \n
Implementierung von Dependency Injection in C#
\n\nDI löst diese Probleme mit drei Hauptansätzen: Injektion über den Konstruktor, über eine Eigenschaft und über eine Methode. Der gebräuchlichste und empfohlene Ansatz ist die Konstruktorinjektion (Constructor Injection).
\n\nSchritt 1. Erstellen einer Schnittstelle
\nZuerst definieren wir eine Abstraktion (Schnittstelle), die den Vertrag zum Senden von Nachrichten beschreibt:
\n\npublic interface IMessageSender\n{\n void Send(string to, string message);\n}\n\nSchritt 2. Implementieren konkreter Dienste
\nJetzt erstellen wir mehrere Implementierungen dieser Schnittstelle:
\n\npublic class EmailSender : IMessageSender\n{\n public void Send(string to, string message)\n {\n Console.WriteLine($"Sende E-Mail an {to}: {message}");\n }\n}\n\npublic class SmsSender : IMessageSender\n{\n public void Send(string to, string message)\n {\n Console.WriteLine($"Sende SMS an {to}: {message}");\n }\n}\n\nSchritt 3. Anwendung von DI über den Konstruktor
\nJetzt erstellt die Klasse NotificationService die Abhängigkeiten nicht mehr selbst, sondern erhält sie über den Konstruktor:
public class NotificationService\n{\n private readonly IMessageSender _messageSender;\n\n // Abhängigkeitsinjektion über den Konstruktor\n public NotificationService(IMessageSender messageSender)\n {\n _messageSender = messageSender;\n }\n\n public void Notify(string userContact, string message)\n {\n _messageSender.Send(userContact, message);\n }\n}\n\nJetzt können wir die Implementierungen leicht austauschen:
\n\n// Verwenden von E-Mail\nvar emailSender = new EmailSender();\nvar emailNotifier = new NotificationService(emailSender);\nemailNotifier.Notify("user@example.com", "Hallo per E-Mail!");\n\n// Verwenden von SMS\nvar sms