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
238: Constructor Injection Best Practices
By now, you're likely comfortable with the basic syntax of constructor injection. You know how to put an interface in your constructor and let the DI container handle the rest. But in a real production codebase, things get messy quickly. You'll start noticing your constructors growing into monsters, or you'll run into weird runtime crashes that don't make sense.
My constructor is getting huge—is there a point where I've added too many dependencies?
Short answer: Yes. If you're hitting 5, 6, or 7 dependencies in a single constructor, your class is screaming at you that it's doing too much. This is a classic "Code Smell" indicating a violation of the Single Responsibility Principle.
I've seen developers try to justify this by saying, "But the OrderProcessor does need to handle taxes, shipping, emails, logging, inventory, and payments!" Sure it does, but it shouldn't be the one coordinating all those low-level details. When I see this, I usually suggest one of two paths: splitting the class into smaller, more focused services, or introducing a "Facade" or "Aggregator" service.
// 🚩 The "Monster" Constructor
public class OrderService
{
public OrderService(
IPaymentProcessor payment,
IEmailService email,
IInventoryClient inventory,
ITaxCalculator tax,
ILogger logger,
IShippingProvider shipping,
IUserPreferences prefs)
{
// This is a nightmare to maintain and even worse to unit test.
}
}
// ✅ The Refactored Approach
// Group related logic into a separate coordinator
public class OrderFulfillmentService
{
private readonly IShippingProvider _shipping;
private readonly IInventoryClient _inventory;
public OrderFulfillmentService(IShippingProvider shipping, IInventoryClient inventory)
{
_shipping = shipping;
_inventory = inventory;
}
// ... logic for fulfillment
}
public class OrderService
{
public OrderService(
IPaymentProcessor payment,
OrderFulfillmentService fulfillment,
INotificationService notification)
{
// Much cleaner. We've delegated the "fulfillment" complexity.
}
}
Do I really need an interface for every single thing I inject?
You'll often hear the "interface everything" mantra. While it's a good rule of thumb, it can lead to a lot of boilerplate code that doesn't actually provide value. The real reason we use interfaces in constructor injection isn't just "because it's a pattern"—it's primarily for testability and decoupling.
If you have a service that just performs a complex calculation and has no external dependencies (like a database or API), an interface might be overkill. However, the moment that class touches the outside world—hitting a file system, a network, or a database—you need that interface. Otherwise, your unit tests will actually be integration tests, which are slow and flaky. My rule of thumb: if I can't imagine a scenario where I'd need to mock this class to keep a test fast and isolated, I might skip the interface. But in 90% of enterprise C# work, you'll stick with the interface.
What should I do if two services depend on each other?
This is the dreaded Circular Dependency. You try to start your app, and the DI container throws a StackOverflowException or a specific "Circular dependency detected" error because ServiceA needs ServiceB, which in turn needs ServiceA.
I'll be honest: if you hit this, don't try to "hack" your way out of it with Lazy<T> or by making one of the dependencies optional. That's just putting a bandage on a broken leg. A circular dependency is a sign that your domain boundaries are blurred. The two services are likely actually one service, or there's a third, hidden responsibility that both of them share.
The fix is almost always to extract the common logic into a third service. If OrderService and CustomerService both need each other to validate an order, move that validation logic into a new OrderValidationService that both can depend on. It feels like you're adding more classes, but you're actually reducing the mental load required to understand how the data flows through your app.
📋 Practical Task
Refactoring the Bloated InvoiceGenerator
You have been handed a legacy InvoiceGenerator class. It currently has 7 dependencies in its constructor, making it nearly impossible to write a unit test for the GenerateInvoice method without spending hours setting up mocks.
Your task: Refactor the InvoiceGenerator by identifying a logical group of dependencies and extracting them into a new "Helper" or "Coordinator" service.
Current Code:
public class InvoiceGenerator
{
public InvoiceGenerator(
IDatabaseConnection db,
ITaxCalculator tax,
ICurrencyConverter currency,
IEmailClient email,
ISmsClient sms,
IPdfGenerator pdf,
ILogger logger)
{
// ...
}
public void GenerateInvoice(int orderId) { /* Logic here */ }
}
Requirements:
- Create a new service (e.g.,
INotificationServiceorITaxService) to encapsulate at least two related dependencies. - Update the
InvoiceGeneratorconstructor to depend on your new service instead of the individual low-level components. - Ensure the final
InvoiceGeneratorconstructor has 4 or fewer parameters.
There are no comments for now.