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
129: Building a Console-Based Inventory Manager
When people first start building "manager" style applications, they usually fall into a specific trap: they think the List<T> is the manager. I've seen countless juniors write a program where the Main method just holds a List<Product> and performs all the logic—adding, removing, and updating—right there in the middle of the UI loop. They assume that as long as the data is stored in a list, they've "managed" the inventory.
Thinking a List<Product> is the Same as an Inventory Manager
Here is why that approach fails the moment your project grows. If your Main method handles the logic, you can't enforce rules. For example, what happens if a user tries to remove 10 items from a stock of 5? If you're just calling list[0].Quantity -= 10 in your main loop, you've just created -5 items out of thin air. Your data is now corrupt, and because the logic is scattered across your UI code, you have to hunt down every single place you modify quantity to fix the bug.
// The "Wrong" Way: Logic leaked into the UI
if (userChoice == "remove") {
var product = products.Find(p => p.Id == id);
product.Quantity -= amount; // Oops! No check for negative stock.
Console.WriteLine("Removed!");
}
Building a Guardrail Around Your Data
The correct way is to treat your InventoryManager as a gatekeeper. The list should be private, hidden away where the rest of the program can't touch it. The only way to change the data is through specific methods that enforce your business rules. I call this "building a guardrail." If the InventoryManager says you can't have negative stock, then it is physically impossible for any other part of the app to create negative stock.
Let's look at how I'd actually structure this. We start with a simple Product record for data, then a manager class that handles the "how" of the inventory.
public record Product(int Id, string Name, int Quantity);
public class InventoryManager
{
private readonly List<Product> _products = new();
public void AddProduct(string name, int initialQuantity)
{
int id = _products.Count > 0 ? _products.Max(p => p.Id) + 1 : 1;
_products.Add(new Product(id, name, initialQuantity));
}
public bool TryRemoveStock(int productId, int amount)
{
var product = _products.FirstOrDefault(p => p.Id == productId);
if (product == null || product.Quantity < amount)
{
return false; // Rule enforced: No negative stock
}
// Since Product is a record (immutable), we replace it
int index = _products.IndexOf(product);
_products[index] = product with { Quantity = product.Quantity - amount };
return true;
}
public IEnumerable<Product> GetAllProducts() => _products.AsReadOnly();
}
Keeping the Console Out of Your Business Logic
Another thing I want you to notice: there is not a single Console.WriteLine inside the InventoryManager. This is critical. Your manager should be "headless." It doesn't know it's being used in a console app; it could just as easily be powering a website or a mobile app. It returns bool values or objects, and it's the Program class's job to decide how to tell the user about it.
When you put the UI in the manager, you make the code impossible to unit test. You can't easily write a test that checks if TryRemoveStock works if the method is busy printing text to a screen. Keep the logic pure, and keep the Console calls in your main loop. It feels like more work upfront, but it's the only way to keep your sanity as the codebase expands.
📋 Practical Task
Implementing a Low-Stock Alert Filter
You have the base InventoryManager and Product logic from the lesson. Your task is to extend the InventoryManager class to support a "Low Stock Report."
- Add a new method to
InventoryManagercalledGetLowStockProducts. - This method should accept an integer parameter called
threshold. - The method must return a collection of all products whose
Quantityis strictly less than the providedthreshold. - In your
Mainmethod, implement a console command that asks the user for a threshold value and then prints the name and current quantity of every product that falls below that limit.
There are no comments for now.