Dependency Injection in C#: A Simple Guide for Beginners

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

What is Dependency Injection (DI) in C# and why is it needed?

\n\n

Dependency 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\n

Imagine 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.

\n\n

The Problem Without Dependency Injection

\n\n

Consider a typical example of bad code where the dependency is created inside the class:

\n\n
public 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\n

Disadvantages of this approach:

\n
    \n
  • High coupling (tight coupling): The NotificationService and EmailService classes are tightly bound to each other.
  • \n
  • Difficulty in testing: It is impossible to replace EmailService with 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
\n\n

Implementing Dependency Injection in C#

\n\n

DI solves these problems using three main approaches: constructor injection, property injection, and method injection. The most common and recommended one is Constructor Injection.

\n\n

Step 1. Create an interface

\n

First, let's define an abstraction (interface) that describes the contract for sending messages:

\n\n
public interface IMessageSender\n{\n    void Send(string to, string message);\n}
\n\n

Step 2. Implement concrete services

\n

Now let's create several implementations of this interface:

\n\n
public 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\n

Step 3. Apply DI via constructor

\n

Now the NotificationService class does not create dependencies itself but receives them through the constructor:

\n\n
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\n

Now 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                

Blogs

Book Recommendations