Start learning free

LESSON 99 OF 347 · ARRAYS I

LINQ Where and Select in C#: Filter, Then Transform

Lesson Read the full explanation

"Keep the elements that pass a test, then transform the survivors" is one of the most common patterns in all of programming: pick the even numbers and square them, take the finished orders and grab their totals, and so on. You already know how to write it with a loop:

var evenSquares = new List<int>();
foreach (var x in nums) {
    if (x % 2 == 0) {
        evenSquares.Add(x * x);
    }
}

C# ships a second way to say the same thing: LINQ (Language Integrated Query). Two of its methods cover this exact pattern:

  • Where(test) — keeps only the elements for which the test returns true (that's the if)
  • Select(transform) — applies a transformation to every element that got through (that's the x * x)

Lambdas and chaining:

The test and the transform are written as lambdas — tiny inline functions. x => x % 2 == 0 reads as "given x, is x even?". The calls chain left to right, and .ToList() at the end turns the result back into a List<int>:

var evenSquares = nums.Where(x => x % 2 == 0).Select(x => x * x).ToList();

One line — same result as the five-line loop. Good to know: Where and Select are lazy. On their own they only describe the query; nothing actually runs until something enumerates it — and ToList() is what forces that to happen here.


The trap:

Where and Select are not methods of List<int>. They are extension methods that only come into scope while using System.Linq; sits at the top of your file. Delete that line and the compiler acts as if LINQ never existed:

p1.cs(12,26): error CS1061: 'List<int>' does not contain a definition for 'Where' and no accessible extension method 'Where' accepting a first argument of type 'List<int>' could be found (are you missing a using directive or an assembly reference?)

Read the part in parentheses — "are you missing a using directive" is the compiler telling you exactly what to fix. In this exercise using System.Linq; is already included for you, but the day you start a file from scratch, this is the error you will meet.


Assignment
Follow the Coding Tutorial and build both versions side by side. Start from var nums = new List<int> { 1, 2, 3, 4, 5, 6 };, then

  • filter the evens and square them with a foreach loop, collecting into loopSquares, and print it,
  • do the same in one line with Where + Select into linqSquares, and print it.

Print each list with string.Join(", ", ...). The exact expected output is:

loop: 4, 16, 36
linq: 4, 16, 36

Hint
The lambda inside Where must produce a bool (x => x % 2 == 0), while the one inside Select produces the new value (x => x * x). Finish the chain with .ToList() so the result is a real list you can print.


STEP 1 OF 8
    Ctrl + Enter to check

    C# · ARRAYS I

    Linq Select Where in C#, explained

    The filter-then-transform pattern

    A very common task is to keep the elements that pass a test and then transform the ones that remain. Pick the even numbers and square them. Take the finished orders and read their totals. With a loop, the pattern has three parts: a result list, an if that decides what to keep, and an expression that builds the new value.

    var evenSquares = new List<int>();
    foreach (var x in nums) {
        if (x % 2 == 0) {
            evenSquares.Add(x * x);
        }
    }
    

    LINQ, short for Language Integrated Query, is a set of methods that say the same thing in one expression. Two of them match this pattern exactly:

    • Where(test) keeps the elements for which the test returns true. It plays the role of the if.
    • Select(transform) builds a new value from each element. It plays the role of the expression inside Add.

    Lambdas and chaining

    The test and the transform are written as lambdas, small inline functions. The arrow => separates the parameter from the result:

    var evenSquares = nums.Where(x => x % 2 == 0).Select(x => x * x).ToList();
    

    Read it left to right: from nums, keep every x where x % 2 == 0, turn each remaining x into x * x, and collect the results into a list. The lambda for Where must produce a bool. The lambda for Select produces the new value, and its type can differ from the input type. For example, names.Select(n => n.Length) turns strings into integers.

    Order matters

    Each call receives the output of the one before it, so Where runs first here and Select sees only the even numbers. Swapping them would square every number first and then test the squares. That would still work for evenness, since a square is even only when its root is, but with other tests the order changes the result. Filtering first also means the transformation runs on fewer elements.

    Lazy evaluation and ToList

    Where and Select are lazy. On their own they only describe a query, and nothing runs until something enumerates it, such as a foreach loop, string.Join or ToList(). Without ToList() the variable holds an IEnumerable<int> that re-runs the query each time it is enumerated:

    var nums = new List<int> { 1, 2, 3, 4, 5, 6 };
    var query = nums.Where(x => x % 2 == 0).Select(x => x * x);
    
    nums.Add(8);
    Console.WriteLine(string.Join(", ", query)); // 4, 16, 36, 64
    

    The 8 was added after the query was written, and it still shows up because the query only ran when string.Join enumerated it. Calling ToList() runs the query once and stores the results in a real List<int>, which will not change when nums does.

    The using directive

    Where and Select are not methods of List<int>. They are extension methods that come into scope only when using System.Linq; is at the top of the file. Without it, the compiler reports error CS1061: List<int> does not contain a definition for Where, and it asks whether you are missing a using directive. That message tells you the fix. In this exercise the directive is already in place, but you will meet the error when you start a file from scratch.

    The exercise, step by step

    The lesson builds both versions side by side, so you can compare them.

    1. Create the data. var nums = new List<int> { 1, 2, 3, 4, 5, 6 }; uses a collection initializer. var lets the compiler infer List<int>.
    2. Make an empty result list named loopSquares. It grows with each Add, so it needs no size.
    3. Open a foreach loop over nums with the variable x, and use braces.
    4. Test for evens. Inside the loop, if (x % 2 == 0). The remainder after dividing by 2 is 0 for even numbers.
    5. Add the square. Inside the if, call loopSquares.Add(x * x), so only values that passed the test are squared and stored.
    6. Print the loop result with string.Join(", ", loopSquares) after the text loop: . string.Join puts the separator between elements only, so there is no trailing comma.
    7. Write the LINQ version in one line: Where for the test, Select for the square and ToList() to materialize the result in linqSquares.
    8. Print it the same way, after the text linq: .
    var nums = new List<int> { 1, 2, 3, 4, 5, 6 };
    var loopSquares = new List<int>();
    foreach (var x in nums) {
        if (x % 2 == 0) {
            loopSquares.Add(x * x);
        }
    }
    Console.WriteLine("loop: " + string.Join(", ", loopSquares));
    var linqSquares = nums.Where(x => x % 2 == 0).Select(x => x * x).ToList();
    Console.WriteLine("linq: " + string.Join(", ", linqSquares));
    

    The program prints:

    loop: 4, 16, 36
    linq: 4, 16, 36
    

    Common mistakes

    • Missing using System.Linq;. This causes the CS1061 error described above.
    • A Where lambda that doesn't return a bool. Writing x => x % 2 is a compile error, because the result is an int. Compare with == 0.
    • Forgetting ToList(). Printing a lazy query with Console.WriteLine directly shows the type name, not the items. Use string.Join or add ToList().
    • Expecting Where to change the original. LINQ never modifies nums. Each query produces new data.

    Key takeaways

    • Where keeps elements that pass a test; Select transforms each element.
    • Both take lambdas, and they chain from left to right.
    • They are extension methods from System.Linq.
    • They are lazy: ToList() runs the query and stores the result.
    Keep going with C#About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free