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
246: Common C# Interview Questions on Dependency Injection
Imagine you're setting up a high-end coffee station in your kitchen. If you buy a machine that has the coffee beans permanently welded inside the hopper, you're stuck. If you decide you hate that roast, or if the beans go stale, you have to throw away the whole machine. That's a "hard-coded dependency."
Now, imagine a machine with a universal filter basket. The machine doesn't care where the beans came from or who roasted them; it just knows that as long as the coffee fits in that specific basket, it can brew a cup. You, the owner, "inject" the coffee of your choice into the machine every morning. The machine provides the capability to brew, but you provide the implementation of the flavor.
In C#, this maps perfectly to Dependency Injection (DI):
- The Machine: This is your high-level class (e.g.,
OrderProcessor). It needs a tool to do a job, but it shouldn't be responsible for creating that tool. - The Filter Basket: This is your Interface (e.g.,
ICoffeeBean). It defines the contract. "I don't care who you are, as long as you behave like coffee beans." - The Specific Roast: This is your concrete implementation (e.g.,
DarkRoastBeansorDecafBeans). - The Person loading the machine: This is the DI Container (like the built-in .NET
IServiceProvider). It handles the "new-ing up" of objects and hands them to the classes that need them.
Answering the "What is DI?" Question Without Sounding Like a Textbook
When an interviewer asks this, they aren't looking for a dictionary definition. They want to know if you understand decoupling. I usually tell them that DI is simply a technique where a class asks for its dependencies through its constructor rather than creating them internally with the new keyword.
// The WRONG way: Hard-coded (Welded beans)
public class OrderService {
private readonly EmailSender _sender = new EmailSender(); // Tight coupling!
public void Process() {
_sender.Send("Order processed!");
}
}
// The RIGHT way: Dependency Injection (The filter basket)
public class OrderService {
private readonly IMessageService _messageService;
public OrderService(IMessageService messageService) {
_messageService = messageService; // The container "injects" this
}
public void Process() {
_messageService.Send("Order processed!");
}
}
The second example is infinitely more testable. I can pass in a MockMessageService during a unit test so I don't accidentally spam a real customer every time I run my test suite. That's the "win" you should highlight in an interview.
Navigating the Three Service Lifetimes
This is the most common technical follow-up. If you get this wrong, it's a red flag because it leads to memory leaks or multi-threading bugs. I think of lifetimes in terms of "who owns the object's life."
- Transient: These are "disposable" objects. The container creates a brand new instance every single time it's requested. Use this for lightweight, stateless services.
- Scoped: These live for the duration of a single request. In a web app, if three different classes in one HTTP request ask for the same Scoped service, they all get the same instance. Once the request ends, the object is disposed. This is perfect for things like a
DbContext. - Singleton: One instance, forever. It's created the first time it's requested and lives until the application shuts down. Be careful here—if a Singleton holds onto a Scoped service, you've just created a "captive dependency," which is a bug that will haunt your logs.
DI vs. DIP: The Nuance that Impresses
A lot of developers use "Dependency Injection" and "Dependency Inversion Principle" interchangeably. They aren't the same. If you can point this out, you'll stand out.
Dependency Inversion (DIP) is the high-level philosophy. It says: "High-level modules should not depend on low-level modules; both should depend on abstractions." It's the goal.
Dependency Injection (DI) is the mechanism we use to achieve that goal. It's the act of passing the dependency in. You use DI to satisfy the requirements of DIP.
📋 Practical Task
Refactoring a Hard-Coded Payment Processor to Use DI
You have been handed a legacy PaymentManager class that is impossible to unit test because it creates a concrete CreditCardProcessor inside its constructor. Your task is to refactor this to follow the Dependency Inversion Principle using Constructor Injection.
Requirements:
- Create an interface named
IPaymentProcessorwith a methodbool ProcessPayment(decimal amount). - Modify the existing
CreditCardProcessorclass to implement this interface. - Refactor the
PaymentManagerclass so that it no longer callsnew CreditCardProcessor(). Instead, it should acceptIPaymentProcessorthrough its constructor. - In your
Program.cs(or a mock setup), demonstrate how you would instantiate thePaymentManagerby passing in theCreditCardProcessor.
// STARTING CODE (Refactor this)
public class CreditCardProcessor {
public bool ProcessPayment(decimal amount) {
Console.WriteLine($"Processing ${amount} via Credit Card...");
return true;
}
}
public class PaymentManager {
private readonly CreditCardProcessor _processor;
public PaymentManager() {
_processor = new CreditCardProcessor(); // This is the problem!
}
public void Checkout(decimal total) {
if (_processor.ProcessPayment(total)) {
Console.WriteLine("Payment Successful!");
}
}
}There are no comments for now.