Skip to Content
Course content

186: Select and SelectMany

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 SelectMany to 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; } 
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.