Dataslope logoDataslope

Filtering and Projection

Where and Select — the two most important LINQ operators, and the mental model that makes every other operator easy

If you only learned two LINQ operators, you'd cover 80% of every data-processing task you'll ever do:

  • Where — keep the elements that satisfy a predicate.
  • Select — transform each element into something else.

These two are the filter and map primitives that almost every functional language has. In modern C#, they're extension methods on IEnumerable<T>. This page walks through them carefully, because understanding them shapes how you think about everything else.

Mental model

Picture a sequence as data flowing through a pipe. Each operator is a stage in the pipe.

Where is a sieve: things either pass through or don't. Select is a converter: each thing that comes in becomes something else on the way out. The shape of the sequence may change (different element type), but the order is preserved.

Where — filtering

Where takes a predicate (Func<T, bool>) and returns a sequence containing only the elements for which the predicate is true.

Code Block
C# 13

Important: an "empty result" is an empty sequence, not null. You can iterate it; you just get zero elements.

There is also a variant of Where that gives you the index of each element:

Code Block
C# 13

The two-argument lambda (name, index) => ... opts into the indexed overload. Useful, but use it sparingly — most code is clearer without indices.

Select — projecting

Select takes a selector (Func<TSource, TResult>) and produces a new sequence with the function applied to each element. This is sometimes called map in other languages.

Code Block
C# 13

Note that the result type follows the lambda's return type. Project ints into strings, strings into lengths, records into anonymous objects — anything you want.

Anonymous and tuple projections

A common pattern: project into a small ad-hoc shape mid-pipeline.

Code Block
C# 13

Use anonymous types when the shape is consumed inside the same expression. Use records when the shape is returned across a method boundary or used in many places.

Composing Where and Select

The real power shows up when you compose. Every pipeline you'll ever write is some combination of filter, then transform.

Code Block
C# 13

Two Where clauses chain naturally — they don't have to be combined into one giant predicate. Each one expresses a single intent; the pipeline as a whole reads top to bottom.

Step-by-step walkthrough

Let's trace what happens for a single element flowing through the pipeline above:

For an order with Product = "Gizmo", the first Where rejects it and neither subsequent operator runs. This is part of what makes LINQ pipelines efficient — the work for "dropped" elements is skipped entirely.

Select is not a loop

A common beginner habit is to write code like:

// Don't do this.
people.Select(p => { Console.WriteLine(p.Name); return p; }).ToList();

Select is for transforming, not for doing. If you want to execute a side effect for each element, use a plain foreach. The LINQ ecosystem leans into the idea that operators are pure and side-effect-free.

Don't use Select for side effects

Using Select for side effects can produce surprising results because LINQ is lazy: nothing happens until the sequence is enumerated. Pair that with a Select whose lambda has side effects, and you'll get weird "why did it run twice?" bugs.

A multi-file challenge

Challenge
C# 13
Names of adults, uppercase

In PeopleFilter.cs, implement AdultNamesUpper(IEnumerable<Person> people) which returns the uppercased Name of every person whose Age >= 18.

Use Where and Select. Do not use foreach.

Program.cs will call your method and print the result one per line.

QuestionSelect one

Given var result = nums.Where(n => n > 10).Select(n => n * 2);, which statement is correct?

The pipeline runs immediately and result is a List<int>.

The Select lambda runs for every element of nums, regardless of the filter.

result is a deferred IEnumerable<int> — neither Where nor Select runs until something enumerates it (e.g. foreach, ToList, Sum).

Where and Select can only be used on List<T> and arrays.

On this page