What is Dependency Injection (DI) in C# and why is it needed?
\n\nDependency Injection is a design pattern that makes code more flexible, testable, and maintainable. Instead of a class creating the objects it depends on itself, these objects are "injected" from the outside. This is one of the key principles underlying modern development in C# and .NET.
\n\nImagine you are writing an application for sending email notifications. If your NotificationService class directly creates an instance of SmtpClient, then when changing the mail server or needing to use a different sending method (e.g., via an API), you will have to rewrite the code. DI solves this problem.
The Problem Without Dependency Injection
\n\nConsider a typical example of bad code where the dependency is created inside the class:
\n\npublic class EmailService\n{\n private SmtpClient _smtpClient;\n\n public EmailService()\n {\n // Tight coupling to a specific implementation\n _smtpClient = new SmtpClient("smtp.gmail.com", 587);\n }\n\n public void SendEmail(string to, string subject, string body)\n {\n // Sending logic\n }\n}\n\npublic class NotificationService\n{\n private EmailService _emailService;\n\n public NotificationService()\n {\n _emailService = new EmailService(); // Tight coupling\n }\n\n public void NotifyUser(string email)\n {\n _emailService.SendEmail(email, "Notification", "Hello!");\n }\n}\n\nDisadvantages of this approach:
\n- \n
- High coupling (tight coupling): The
NotificationServiceandEmailServiceclasses are tightly bound to each other. \n - Difficulty in testing: It is impossible to replace
EmailServicewith a mock object during unit testing. \n - Difficulty in extension: If you need to add SMS notifications, you will have to change the logic inside
NotificationService. \n
Implementing Dependency Injection in C#
\n\nDI solves these problems using three main approaches: constructor injection, property injection, and method injection. The most common and recommended one is Constructor Injection.
\n\nStep 1. Create an interface
\nFirst, let's define an abstraction (interface) that describes the contract for sending messages:
\n\npublic interface IMessageSender\n{\n void Send(string to, string message);\n}\n\nStep 2. Implement concrete services
\nNow let's create several implementations of this interface:
\n\npublic class EmailSender : IMessageSender\n{\n public void Send(string to, string message)\n {\n Console.WriteLine($"Sending email to {to}: {message}");\n }\n}\n\npublic class SmsSender : IMessageSender\n{\n public void Send(string to, string message)\n {\n Console.WriteLine($"Sending SMS to {to}: {message}");\n }\n}\n\nStep 3. Apply DI via constructor
\nNow the NotificationService class does not create dependencies itself but receives them through the constructor:
public class NotificationService\n{\n private readonly IMessageSender _messageSender;\n\n // Dependency injection via constructor\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\nNow we can easily swap implementations:
\n\n// Using Email\nvar emailSender = new EmailSender();\nvar emailNotifier = new NotificationService(emailSender);\nemailNotifier.Notify("user@example.com", "Hello via email!");\n\n// Using SMS\nvar sms