C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
111: Dependency Injection as a Pattern
Imagine you're building a high-end coffee machine. If you weld the water tank directly into the chassis, the machine works great—until the tank leaks or you decide you want a bigger one. Now, to change the tank, you have to take a blowtorch to the entire machine. That's a nightmare.
Instead, you design the machine with a standard hose connection on the back. The machine doesn't care if the water comes from a plastic jug, a fancy filtration system, or directly from the city plumbing. As long as the source fits that hose and provides water, the machine does its job. You've decoupled the "making coffee" logic from the "getting water" logic.
In C#, Dependency Injection (DI) is that hose connection. Here is how that maps to your code:
- The Coffee Machine is your high-level class (the one that does the main work).
- The Water Source is the dependency (a service, a database, or an API client).
- The Hose Connection is the Interface. It defines the contract of what the dependency must do, without caring how it does it.
- The Act of Plugging it in is the "Injection."
The Problem with Hard-Coding Your Dependencies
Most developers start by doing things the "welded" way. Let's say you're building a system to send order confirmations. You might write something like this:
public class OrderProcessor
{
private readonly EmailSender _emailSender = new EmailSender();
public void ProcessOrder(Order order)
{
// Process the order...
_emailSender.Send("Order confirmed!");
}
}
At first glance, this is fine. But look closely: OrderProcessor is now married to EmailSender. If your boss tells you tomorrow that confirmations should go via SMS, or if you want to write a unit test without actually sending 500 real emails to your customers, you're stuck. You have to change the code inside OrderProcessor, which risks breaking your order logic just to change a notification method. I've spent way too many hours fixing bugs introduced by "simple" changes like this.
Letting the Outside World Decide
To fix this, we stop using the new keyword inside our classes. Instead, we define an interface and ask for it in the constructor. This is the heart of the DI pattern.
public interface IMessageService
{
void Send(string message);
}
public class OrderProcessor
{
private readonly IMessageService _messageService;
// We don't create the service; we just demand that someone gives us one.
public OrderProcessor(IMessageService messageService)
{
_messageService = messageService;
}
public void ProcessOrder(Order order)
{
// Process the order...
_messageService.Send("Order confirmed!");
}
}
Now, OrderProcessor is blissfully ignorant of how the message is sent. It just knows that whatever object it receives can call Send(). You've effectively moved the decision-making process "up the chain" to the part of your app that starts everything up.
Why We Actually Do This (Beyond Just Being Fancy)
You might be thinking, "This feels like a lot of extra typing just to avoid a new keyword." Trust me, it's worth it for two main reasons.
First: Testability. In a unit test, you don't want to hit a real database or a real email API. With DI, you can inject a "Mock" or "Fake" version of the service that just records that the method was called, making your tests lightning fast and reliable.
Second: Parallel Development. I can write the OrderProcessor logic while a teammate writes the SmsService. As long as we both agree on the IMessageService interface, our code will slot together perfectly without us needing to wait for each other to finish.
📋 Practical Task
Refactor the PaymentGateway for Multiple Providers
You have been handed a PaymentProcessor class that is currently "welded" to a specific PayPal implementation. Your goal is to refactor this using the Dependency Injection pattern so it can support any payment provider (like Stripe or Square) without changing the processor's internal code.
Requirements:
- Create an interface named
IPaymentProviderwith a methodbool ProcessPayment(decimal amount). - Create a class
PayPalProviderthat implements this interface (simply returntrueand print "Processing via PayPal"). - Modify the
PaymentProcessorclass to remove the hard-codednew PayPalProvider()call. - Implement Constructor Injection in
PaymentProcessorto receive anIPaymentProvider. - In your
Mainmethod, instantiate aPayPalProviderand inject it into thePaymentProcessor.
// Start with this coupled code:
public class PayPalProvider
{
public bool ProcessPayment(decimal amount) => true;
}
public class PaymentProcessor
{
private readonly PayPalProvider _provider = new PayPalProvider();
public void Checkout(decimal total)
{
_provider.ProcessPayment(total);
}
}There are no comments for now.