← Open in the full interactive course (progress tracking, search & more)

Filtering decides which items survive. Projection decides what shape they come out in.

Filtering with Where narrows a sequence down — but the items that survive still look exactly like they did going in. Very often that's not what you actually want to hand back to a caller. An API shouldn't return your internal Employee entity, salary and all, to a public endpoint. A report doesn't need the full Order object — just the customer name and total. Projection is how LINQ reshapes each item into something new.

This lesson covers Select for one-to-one transformation, and SelectMany for flattening nested collections into a single sequence.

What Is It?

The Simple Explanation

Projection means transforming each item in a sequence into something else — a different type, a subset of its data, a computed value. Select does this one item at a time, one-in-one-out. SelectMany does it when each item produces its own sub-sequence, and you want all of those sub-sequences merged into one flat result.

The Technical Definition

public static IEnumerable<TResult> Select<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector) public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)

Select is generic over two type parameters — the input type TSource and a completely independent output type TResult. That's the essential difference from Where: Where always returns IEnumerable<TSource> (same type in, same type out); Select can return an entirely different shape. SelectMany's selector returns an IEnumerable<TResult> per item, and the method flattens all of those inner sequences into one single, flat IEnumerable<TResult>.

Why Does It Exist?

Without a dedicated transform operator, reshaping data means an accumulator and a manual loop — the same pattern you've now seen (and moved past) several times:

// Manual loop, entity → DTO var dtos = new List<ProductDto>(); foreach (var p in products) { dtos.Add(new ProductDto(p.Name, p.Price)); }
// Select var dtos = products.Select(p => new ProductDto(p.Name, p.Price));

The "entity to DTO" transformation shown above is one of the single most common uses of LINQ in real applications — every API layer that shouldn't expose its internal domain models directly does exactly this. SelectMany solves a related but distinct problem: what happens when each item doesn't map to one output value, but to a whole collection of them — like each order having multiple line items, and you want a flat list of every line item across every order?

Big Picture

Select vs SelectMany
Select — one in, one out
[Order1, Order2, Order3]
↓ Select(o => o.CustomerName)
["Ana", "Ben", "Cara"]
SelectMany — one in, many out, flattened
[Order1[Line1,Line2], Order2[Line3]]
↓ SelectMany(o => o.Lines)
[Line1, Line2, Line3]

How It Works

SelectMany, STEP BY STEP
1. FOR EACH OUTER ITEM, RUN THE SELECTOR
2. YIELD EVERY ITEM FROM THAT INNER SEQUENCE
3. MOVE TO THE NEXT OUTER ITEM AND REPEAT

Simple Example

public record Product(int Id, string Name, string Category, decimal Price, int Stock); public record ProductDto(string Name, decimal Price); List<Product> products = [ new(1, "Wireless Mouse", "Electronics", 24.99m, 120), new(2, "Standing Desk", "Furniture", 349.00m, 15), ]; // Select — reshape each Product into a ProductDto IEnumerable<ProductDto> dtos = products.Select(p => new ProductDto(p.Name, p.Price)); // Select — project into an anonymous type (useful for quick, throwaway shapes) var summaries = products.Select(p => new { p.Name, DiscountedPrice = p.Price * 0.9m }); foreach (var s in summaries) Console.WriteLine($"{s.Name}: ${s.DiscountedPrice:F2}"); // Wireless Mouse: $22.49 // Standing Desk: $314.10 // Select has an index-aware overload too, just like Where var numbered = products.Select((p, index) => $"{index + 1}. {p.Name}"); foreach (var line in numbered) Console.WriteLine(line); // 1. Wireless Mouse // 2. Standing Desk

SelectMany — Flattening Nested Collections

public record Order(int Id, string CustomerName, List<string> LineItems); List<Order> orders = [ new(1, "Ana", ["Mouse", "Keyboard"]), new(2, "Ben", ["Desk Lamp"]), new(3, "Cara", ["Headphones", "Chair", "Mouse"]), ]; // Select alone gives you a sequence OF sequences — not flat IEnumerable<List<string>> nested = orders.Select(o => o.LineItems); // [["Mouse","Keyboard"], ["Desk Lamp"], ["Headphones","Chair","Mouse"]] // SelectMany flattens into one single sequence of items IEnumerable<string> allItems = orders.SelectMany(o => o.LineItems); // ["Mouse", "Keyboard", "Desk Lamp", "Headphones", "Chair", "Mouse"] foreach (var item in allItems) Console.WriteLine(item);

Code → Meaning → Result: Select alone would give you three lists inside one outer sequence — technically correct, but not what you usually want when you're asking "every item ordered, across every order." SelectMany collapses that nesting into a single flat list of six strings.

There's also an overload of SelectMany that lets you keep a reference to the outer item alongside each flattened inner item — useful when you need both:

var itemsWithCustomer = orders.SelectMany( o => o.LineItems, (order, item) => new { order.CustomerName, Item = item }); foreach (var x in itemsWithCustomer) Console.WriteLine($"{x.CustomerName} ordered {x.Item}"); // Ana ordered Mouse // Ana ordered Keyboard // Ben ordered Desk Lamp // Cara ordered Headphones // Cara ordered Chair // Cara ordered Mouse

Real-World Example

A web API exposes a product catalog endpoint. The internal Product entity carries fields (a database primary key, an internal supplier reference) that should never reach a public client. Projecting to a DTO at the API boundary is the standard, idiomatic pattern:

public record Product(int Id, string Name, string Category, decimal Price, int Stock, string SupplierCode); public record ProductApiResponse(string Name, string Category, decimal Price, bool InStock); [HttpGet("api/products")] public IActionResult GetProducts() { var response = products.Select(p => new ProductApiResponse( p.Name, p.Category, p.Price, p.Stock > 0)); return Ok(response); }

Note that Id (internal database key) and SupplierCode (internal-only field) never make it into ProductApiResponse at all — Select is the tool doing that boundary-shaping work, and this exact pattern shows up at nearly every API layer in real .NET applications.

Analogy

A Factory Assembly Line

Select is a single assembly-line station: a part comes in, a transformation happens, a (possibly very different-looking) part comes out — one in, one out, every time. SelectMany is a station where one incoming part is disassembled into several separate parts that all continue down the line individually, rather than staying bundled together as a single "kit."

Under the Hood

SelectMany, CONCEPTUALLY
public static IEnumerable<TResult> SelectMany<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TResult>> selector)
{
    foreach (var outerItem in source)
    {
        foreach (var innerItem in selector(outerItem))
        {
            yield return innerItem;
        }
    }
}

Like Where, both Select and SelectMany are yield return iterators — nothing runs until enumerated, and each output item is produced lazily, one at a time, as the consuming code asks for it.

Common Confusion

1. "Select filters too" — no, it never removes items

Select always produces exactly as many output items as input items — one-to-one, always. If you need fewer items and a different shape, that's Where followed by Select, not Select alone.

2. Select returning a list of lists vs SelectMany flattening them

The single most common mistake when working with nested data is reaching for Select when you actually want a flat result. If your projection's lambda itself returns a collection (o => o.LineItems) and you want every item from every inner collection merged into one sequence, you want SelectMany, not Select.

Common Mistakes

Mistake 1 — Using Select when you meant SelectMany

orders.Select(o => o.LineItems) gives you an IEnumerable<List<string>> — a sequence of lists, one per order — not the flat list of every line item you probably wanted. Use SelectMany whenever your projection returns a collection you want merged, not nested.

Mistake 2 — Projecting before filtering, doing extra unnecessary work

products.Select(p => new ProductDto(p.Name, p.Price)).Where(d => d.Price > 50) constructs a DTO for every product, including ones that will immediately be filtered out. Filter first, then project: products.Where(p => p.Price > 50).Select(p => new ProductDto(p.Name, p.Price)) — fewer objects allocated, same result. This ordering matters and is covered again in the LINQ Performance lesson at the end of this module.

When Should I Use It?

Use Select when

Use SelectMany when

Mental Model

Select = "turn each item into something else, one-for-one"
SelectMany = "each item gives me a group of things — merge every group into one flat list"

Remember:
· Select never changes the count of items; it only changes their shape.
· If your Select lambda returns a collection, you probably wanted SelectMany.
· Filter (Where) before you project (Select) — don't build shapes you're about to throw away.

Key Takeaway


Check Your Understanding

You've learned how to reshape data with Select and flatten it with SelectMany. Let's check your understanding.

1. What is the fundamental difference between Where and Select?

Show answer

Correct: B

Why B is correct: Where is about which items survive, unchanged. Select is about what shape every surviving item comes out in — they answer different questions entirely.

Why A is incorrect: They serve opposite purposes — narrowing a set vs. transforming a set.

Why C is incorrect: This reverses their actual roles.

Why D is incorrect: Select can be used standalone, or before or after Where — though as covered in Common Mistakes, filtering first is usually more efficient.

Reinforcement: "Filter narrows; project reshapes" is the core distinction to carry forward.

2. A list of Order objects each has a List<string> LineItems property. You want one single flat list containing every line item across all orders. Which operator is correct?

Show answer

Correct: C

Why C is correct: SelectMany is built exactly for this shape: each order contributes a collection, and all of those collections get merged into one flat sequence of line items.

Why A is incorrect: Select alone would give a sequence of lists — one list per order — not a single flat list.

Why B is incorrect: This wouldn't even compile — Where's predicate must return bool, not a List<string>.

Why D is incorrect: OfType<T> filters by runtime type within a single sequence — it has nothing to do with flattening nested collections.

Reinforcement: Whenever your projection would return a collection per item, reach for SelectMany instead of Select.

3. Why is entity-to-DTO mapping considered a real-world, idiomatic use of Select?

Show answer

Correct: B

Why B is correct: As shown in the real-world example, Select reshapes each internal entity into a DTO that intentionally omits fields like internal IDs or supplier codes — a standard pattern at API boundaries.

Why A is incorrect: Nothing in C# requires Select for API responses — it's simply a clean, idiomatic way to do the mapping.

Why C is incorrect: Select performs no security-related behavior automatically — omitting fields is something the developer's projection does deliberately.

Why D is incorrect: DTOs are ordinary types and can be constructed anywhere, not exclusively inside Select — it's just a very natural place to do so.

Reinforcement: Projection is the mechanism; keeping your public API shape distinct from your internal entities is the design goal it serves.

4. Why is products.Where(p => p.Price > 50).Select(...) generally preferred over products.Select(...).Where(d => d.Price > 50)?

Show answer

Correct: B

Why B is correct: As covered in Common Mistakes, projecting every item before filtering wastes allocations on objects that are about to be thrown away — filtering first means only the items that matter get transformed.

Why A is incorrect: Both versions compile fine, assuming the projected type also has a comparable Price-like member to filter on.

Why C is incorrect: Both orderings can produce the same final set of results — the difference is efficiency, not correctness, in this case.

Why D is incorrect: There's no such language rule — LINQ operators can be chained in any order that makes logical sense; efficiency is a design choice, not a compiler requirement.

Reinforcement: Ordering operators to filter before you project is a small habit with a real payoff — revisited fully in this module's closing lesson on LINQ performance.

You can now filter and reshape sequences confidently. Next up: putting results into a meaningful order with OrderBy and friends.


dotnetmadeeasy.com — Learn C# and .NET, the right way.