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
186: Select and SelectMany
You've likely already spent some time with Select. It's the bread and butter of LINQ—you have a list of things, and you want a list of something else derived from those things. It's a one-to-one mapping. But the moment you deal with nested data—like a list of orders where each order contains a list of line items—Select starts to feel like it's fighting you. I've seen plenty of developers struggle here, trying to force a square peg into a round hole because they aren't quite sure when to switch to SelectMany.
The "Collection of Collections" headache
Let's look at a real-world scenario. Imagine you're building a system for a music library. You have a Playlist class, and each playlist has a List<Song>. Now, say you want to get a master list of every song across all your playlists so you can check for duplicates or generate a global report. Your first instinct might be to use Select because you're "selecting" the songs.
var allSongs = playlists.Select(p => p.Songs);
Here is where the frustration kicks in. If you hover over allSongs in your IDE, you'll see the type isn't IEnumerable<Song>. It's IEnumerable<List<Song>>. You haven't actually retrieved the songs; you've retrieved a collection of lists. To actually get to the songs, you'd have to write a nested foreach loop, which completely defeats the purpose of using LINQ in the first place. It's clunky, it's verbose, and it's exactly the "naive" path that leads to overly complex code.
Flattening the hierarchy
This is exactly why SelectMany exists. While Select transforms each element into exactly one output, SelectMany transforms each element into a sequence, and then it "flattens" all those sequences into a single, linear list. It's as if it's saying: "Go into every one of these playlists, grab all the songs inside, and dump them all into one big bucket for me."
// This gives us a flat IEnumerable<Song>
var allSongs = playlists.SelectMany(p => p.Songs);
Now, allSongs is exactly what you wanted: a simple list of songs. I find it helpful to think of SelectMany as a Select followed by a Flatten operation. If you see yourself writing a Select and then immediately wanting to iterate through the results with another loop, you're almost certainly looking for SelectMany.
Choosing based on the output shape
The trade-off here isn't about performance—though SelectMany is quite efficient—it's about the "shape" of the data you need. If you want to keep the relationship between the parent and the child intact (e.g., you want to know which songs belong to which specific playlist), stick with Select. You keep your groups separate.
However, if the parent object is just a vehicle to get to the children, and you only care about the children in aggregate, SelectMany is the tool for the job. It strips away the grouping and gives you a clean, one-dimensional stream of data. Just be careful: if your child collections can be null, SelectMany will throw a NullReferenceException when it tries to flatten them. I always suggest adding a quick null check or ensuring your properties are initialized to empty lists to avoid those annoying runtime crashes.
📋 Practical Task
Flattening Order Item Categories for a Sales Report
You are working on a reporting module for an e-commerce platform. You have a list of Order objects. Each Order has a property Items, which is a List<OrderItem>. Each OrderItem has a Category string (e.g., "Electronics", "Books", "Clothing").
Your goal is to create a unique list of every category that has been purchased across all orders.
Requirements:
- Use
SelectManyto extract all categories from all orders into a single flat sequence. - Use
Distinct()to ensure each category only appears once in the final result. - The final output should be an
IEnumerable<string>containing the unique categories.
public class Order
{
public int OrderId { get; set; }
public List<OrderItem> Items { get; set; }
}
public class OrderItem
{
public string ProductName { get; set; }
public string Category { get; set; }
}There are no comments for now.