LINQ: Where, Select, OrderBy
Picture a conveyor belt in a factory. Boxes ride along the belt past stations. One station takes off the faulty boxes, another repacks the contents into new packaging, a third lines the boxes up by size. Each station does one job, and together they turn a stream of boxes into what is needed.
A list in a program often needs the same. Say you have to take the numbers greater than three from a list. With a loop that is eight lines: a new list, foreach, if, Add and the braces. Yet the idea itself fits into a few words: "keep the numbers greater than three".
For such tasks .NET has LINQ, a set of methods that filter, transform and sort the items of a sequence: a list, an array, anything foreach can go through. The LINQ methods live in the System.Linq namespace, and a .NET 10 program brings it in by itself, as it does System.
List<int> nums = [5, 2, 8, 1, 9, 4];
var big = nums.Where(n => n > 3);
var doubled = nums.Select(n => n * 2);
var sorted = nums.OrderBy(n => n);
Console.WriteLine(string.Join(" ", big)); // 5 8 9 4
Console.WriteLine(string.Join(" ", doubled)); // 10 4 16 2 18 8
Console.WriteLine(string.Join(" ", sorted)); // 1 2 4 5 8 9Each method gets a lambda, the work of its station, and returns a new sequence. Let's go through them one by one.
Three stations of the belt
Where filters
Where calls the lambda for each item and keeps those for which it returned true. The order does not change: 5 8 9 4 come in the same order as in the list.
Select transforms
Select calls the lambda for each item and puts what it returned into the result. There are as many items as before, but the values are new. It is the same Transform we wrote in the lesson on methods as values.
The name is misleading: Select does not pick anything, it transforms each item. The result's type may differ from the source's: words.Select(w => w.Length) turns strings into numbers.
OrderBy and OrderByDescending sort
OrderBy sorts in ascending order of the key the lambda returns. n => n means "sort by the number itself". OrderByDescending sorts in descending order. You choose the key:
string[] words = ["pear", "fig", "apple"];
var byLength = words.OrderBy(w => w.Length);
var lengths = words.Select(w => w.Length);
var desc = words.OrderByDescending(w => w);
Console.WriteLine(string.Join(" ", byLength)); // fig pear apple
Console.WriteLine(string.Join(" ", lengths)); // 4 3 5
Console.WriteLine(string.Join(" ", desc)); // pear fig appleBy length, the three-letter fig comes first, although it is second in alphabetical order. And OrderByDescending(w => w) sorts the strings themselves, from pear down to apple.
Where or Select
Interviewers often ask you to explain the difference between these methods:
Wherepicks items and does not change them: the result may be shorter than the source;Selectchanges each item and picks nothing: the result has the same length.
What a query returns
A LINQ method call, or a chain of such calls, is called a query. A query returns not a list but a sequence you can go through in foreach or pass to string.Join. Its type depends on the methods in the chain: after Where it is IEnumerable<int>, and after OrderBy it is a different one. You do not need to memorize these types: a query variable is declared with var, and the compiler fills in the type itself.
You cannot print a query directly: Console.WriteLine(big) prints not the numbers but the name of an internal type. That is why the examples join the items with string.Join.
A chain of methods
LINQ methods go one after another, like stations on one belt. Each one gets what the previous one handed on:
List<int> nums = [5, 2, 8, 1, 9, 4];
var result = nums
.Where(n => n % 2 == 0)
.OrderBy(n => n)
.Select(n => n * 10);
Console.WriteLine(string.Join(" ", result)); // 20 40 80
Console.WriteLine(string.Join(" ", nums)); // 5 2 8 1 9 4
List<int> tens = result.ToList();
Console.WriteLine(tens.Count); // 3Let's take the chain apart:
Where(n => n % 2 == 0)keeps the even numbers:2 8 4.OrderBy(n => n)sorts them:2 4 8.Select(n => n * 10)multiplies each by10:20 40 80.numsdid not change: LINQ does not touch the source list, it builds a new sequence.result.ToList()collects the query's items into a newList<int>. To collect them into an array, there is the same kind of method,ToArray().
The order of the stations matters. With Select first, Where would check numbers that were already multiplied.
Here the conveyor belt and LINQ part ways. In a factory a station changes the box itself, but a LINQ method leaves the source list alone: it hands on new values.
Common mistake
The expectation was that Where would remove the extra items from the list itself:
List<int> nums = [5, 2, 8, 1];
nums.Where(n => n > 3); // ✗
Console.WriteLine(string.Join(" ", nums)); // 5 2 8 1The program compiles and prints 5 2 8 1: the list did not change. Where returns a new sequence, but here it was not stored anywhere, and the compiler gives no warning about it.
Store the result: var big = nums.Where(n => n > 3);, or, if you need a list, List<int> big = nums.Where(n => n > 3).ToList();. Removing items from the list itself is the job of its method RemoveAll.
Key points
- LINQ is a set of methods for sequences; a .NET 10 program brings in
System.Linqby itself. Wherepicks items by a condition,Selectturns each item into a new value,OrderByandOrderByDescendingsort by a key.- LINQ methods do not change the source list but return a new sequence;
ToList()andToArray()collect it into a list or an array.