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
187: Where and Filtering Predicates
When you first start working with collections in C#, your instinct is usually to reach for the foreach loop. It's reliable, and you have total control. But as soon as you need to extract a subset of data—say, finding all the "Overdue" invoices in a billing system—that control starts to feel like a chore. You end up writing the same boilerplate over and over: initialize a new empty list, loop through the source, check a condition, and manually add the matching items to the new list.
The manual loop grind
I've seen this pattern in countless code reviews. It usually looks something like this:
var expensiveProducts = new List<Product>();
foreach (var p in allProducts)
{
if (p.Price > 1000 && p.Category == "Electronics")
{
expensiveProducts.Add(p);
}
}
Now, there is nothing technically "wrong" with this. It works, and it's easy to debug. However, you're spending three or four lines of code describing how to filter the list rather than what you actually want. You're managing the state of a temporary list, handling the iteration, and nesting your logic inside an if block. It's noisy. When you have a file with ten of these blocks, the actual business logic—the price and category check—gets buried in the plumbing.
Declarative filtering with Where
This is where .Where() comes in. It allows you to move from an imperative style (do this, then do that) to a declarative style (give me the items that match this criteria). Instead of building a new list manually, you pass a predicate to the Where method. A predicate is just a fancy name for a function that takes an object and returns a boolean: true if it stays, false if it goes.
Here is how I'd write that same logic today:
var expensiveProducts = allProducts.Where(p => p.Price > 1000 && p.Category == "Electronics");
Notice how the noise is gone. The p => ... part is a lambda expression. I'm telling C#, "For every element p in the collection, apply this logic." It's cleaner, it's more readable, and it's significantly harder to accidentally introduce a bug (like forgetting to initialize the results list) because the framework handles the heavy lifting.
Execution timing and the predicate cost
There is one detail here that often trips people up: Where doesn't actually filter the list the moment you call it. It uses deferred execution. When you write that line, C# isn't looping through your products yet; it's just building a plan for how to filter them later. The actual work happens when you iterate over expensiveProducts in a foreach loop or call .ToList().
You might wonder if this is slower than a manual loop. In 99% of application code, the difference is negligible. You're trading a few CPU cycles for a massive gain in maintainability. The only time I'd suggest going back to the manual loop is if you are in a high-frequency trading loop or a game engine update tick where every nanosecond is audited. For everything else, the clarity of the predicate wins.
- Imperative: Focuses on the process (create list $\rightarrow$ loop $\rightarrow$ check $\rightarrow$ add).
- Declarative: Focuses on the result (filter by these criteria).
📋 Practical Task
Filter High-Risk Security Alerts
You are working on a security dashboard. You have a list of SecurityAlert objects, and you need to isolate only the alerts that are marked as "Critical" and occurred within the last 24 hours.
Requirements:
- Create a class
SecurityAlertwith propertiesSeverity(string),Timestamp(DateTime), andMessage(string). - Create a
List<SecurityAlert>populated with at least five different alerts (some critical, some low, some old, some recent). - Use the
.Where()method to filter the list. - The predicate should check if
Severity == "Critical"AND if theTimestampis greater thanDateTime.Now.AddDays(-1). - Print the messages of the filtered alerts to the console.
There are no comments for now.