Dependency Injection Basics
Part of the Object Oriented Programming section of Coddy's C# journey — lesson 46 of 70.
Dependency Injection (DI) is a technique where a class receives its dependencies from the outside rather than creating them itself. This makes code more flexible, testable, and loosely coupled.
Without DI, a class creates its own dependencies, making it hard to change or test:
public class OrderService
{
private EmailSender sender = new EmailSender(); // Tightly coupled
public void PlaceOrder() => sender.Send("Order placed");
}With DI, dependencies are passed in through the constructor:
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine("Email: " + message);
}
public class OrderService
{
private IMessageSender sender;
public OrderService(IMessageSender sender)
{
this.sender = sender;
}
public void PlaceOrder() => sender.Send("Order placed");
}Now OrderService doesn't know or care whether it's using email, SMS, or a mock sender for testing. The dependency is "injected" when the object is created:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email: Order placedThis pattern follows the principle of depending on abstractions (interfaces) rather than concrete implementations. It enables swapping implementations without modifying the class that uses them, which is essential for unit testing and maintaining flexible architectures.
Challenge
EasyLet's build a logging system that demonstrates the power of dependency injection. Instead of hardcoding how logs are written, you'll create a flexible Logger class that can work with any logging destination - console, file simulation, or anything else - without knowing the specifics.
You'll organize your code across three files:
ILogWriter.cs: Define an interface calledILogWriterin theLoggingnamespace. This interface represents the contract for any logging destination and should have a single methodWrite(string message)that returns a string describing where the message was written.LogWriters.cs: Create two concrete implementations ofILogWriterin theLoggingnamespace:ConsoleLogWriter- itsWritemethod returnsConsole: {message}FileLogWriter- takes a filename through its constructor and itsWritemethod returnsFile[{filename}]: {message}
Logger.cs: Create aLoggerclass in theLoggingnamespace that receives its dependency through constructor injection. TheLoggershould:- Accept an
ILogWriterthrough its constructor and store it in a private field - Have a
Log(string message)method that delegates to the injected writer and returns the result
- Accept an
Program.cs: Demonstrate dependency injection by creating two different loggers - one with aConsoleLogWriterand one with aFileLogWriter. Show how the sameLoggerclass behaves differently based on which dependency is injected.
You will receive two inputs:
- The filename for the file logger
- The message to log
Create a Logger with a ConsoleLogWriter and log the message. Then create another Logger with a FileLogWriter (using the filename from input) and log the same message. Print each result on its own line.
For example, if the inputs are app.log and System started, the output should be:
Console: System started
File[app.log]: System startedNotice how the Logger class never changes - it works with any ILogWriter implementation. This is the essence of dependency injection: the Logger depends on an abstraction (the interface), and the concrete implementation is "injected" from outside. You could easily add a DatabaseLogWriter or CloudLogWriter later without modifying the Logger class at all!
Cheat sheet
Dependency Injection (DI) is a technique where a class receives its dependencies from the outside rather than creating them itself, making code more flexible, testable, and loosely coupled.
Without DI (tightly coupled):
public class OrderService
{
private EmailSender sender = new EmailSender(); // Tightly coupled
public void PlaceOrder() => sender.Send("Order placed");
}With DI (loosely coupled):
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine("Email: " + message);
}
public class OrderService
{
private IMessageSender sender;
public OrderService(IMessageSender sender)
{
this.sender = sender;
}
public void PlaceOrder() => sender.Send("Order placed");
}Injecting the dependency:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email: Order placedDI follows the principle of depending on abstractions (interfaces) rather than concrete implementations, enabling swapping implementations without modifying the class that uses them.
Try it yourself
using System;
using Logging;
class Program
{
public static void Main(string[] args)
{
// Read inputs
string filename = Console.ReadLine();
string message = Console.ReadLine();
// TODO: Create a Logger with a ConsoleLogWriter and log the message
// Print the result
// TODO: Create another Logger with a FileLogWriter (using the filename) and log the same message
// Print the result
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesNamespaces & DirectivesIntro to Classes & ObjectsThe 'this' KeywordMethods and ParametersFields vs PropertiesConstructorsObject InitializersRecap - Simple Calculator4Inheritance
Basic Inheritance (:) SyntaxThe 'base' KeywordVirtual & Override KeywordsSealed ClassesThe 'object' Base ClassRecap - Employee Hierarchy7Advanced Features
Operator OverloadingIndexers (this[])ToString() OverrideExtension MethodsRecap - Custom List2Properties & Static Members
Auto-Implemented PropertiesRead/Write-Only PropertiesStatic Fields & MethodsStatic ClassesExpression-Bodied Members5Polymorphism & Interfaces
Compile vs Runtime PolyInterface vs Abstract ClassMultiple InterfacesExplicit InterfacesUpcasting & DowncastingRecap - Shape Calculator8Advanced OOP Concepts
Composition over InheritanceGenerics (Classes & Methods)Delegates and EventsAttributes and ReflectionIDisposable & using StatementDependency Injection Basics