LINQ: Where, Select, OrderBy
Представьте конвейер на фабрике. Коробки едут по ленте мимо станций. Одна станция снимает коробки с браком, другая перекладывает содержимое в новую упаковку, третья расставляет коробки по размеру. Каждая станция делает одно дело, а вместе они превращают поток коробок в то, что нужно.
Со списком в программе часто нужно то же самое. Допустим, из списка надо взять числа больше трёх. Циклом это восемь строк: новый список, foreach, if, Add и фигурные скобки. А сама мысль умещается в четыре слова: «оставь числа больше трёх».
Для таких задач в .NET есть LINQ — набор методов, которые отбирают, преобразуют и сортируют элементы последовательности: списка, массива — всего, что можно перебрать в foreach. Методы LINQ лежат в пространстве имён System.Linq, и программа .NET 10 подключает его сама, как и 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 9Каждый метод получает лямбду — работу своей станции — и возвращает новую последовательность. Давайте разберём их по очереди.
Три станции конвейера
Where — отбор
Where вызывает лямбду для каждого элемента и оставляет те, для которых она вернула true. Порядок не меняется: 5 8 9 4 идут так же, как в списке.
Select — преобразование
Select вызывает лямбду для каждого элемента и кладёт в результат то, что она вернула. Элементов столько же, сколько было, а значения новые. Это тот же Transform, который мы писали в уроке о методах как значениях.
Название сбивает с толку: Select ничего не выбирает, а преобразует каждый элемент. Тип результата может отличаться от исходного: words.Select(w => w.Length) превращает строки в числа.
OrderBy и OrderByDescending — сортировка
OrderBy сортирует по возрастанию ключа, который возвращает лямбда. Лямбда n => n значит «сортировать по самому числу». OrderByDescending сортирует по убыванию. Ключ выбираете вы:
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 appleПо длине fig из трёх букв идёт первым, хотя по алфавиту он второй. А OrderByDescending(w => w) сортирует сами строки, от pear к apple.
Where или Select
На собеседовании часто просят объяснить разницу между этими методами:
Whereотбирает элементы и не меняет их: результат бывает короче исходной последовательности;Selectменяет каждый элемент и ничего не отбирает: результат той же длины.
Что возвращает запрос
Вызов метода LINQ или цепочку таких вызовов называют запросом. Запрос возвращает не список, а последовательность, которую можно перебрать в foreach или передать в string.Join. Её тип зависит от методов цепочки: после Where это IEnumerable<int>, а после OrderBy уже другой. Запоминать эти типы не нужно: переменную запроса объявляют через var, и компилятор подставит тип сам.
Напечатать запрос напрямую не выйдет: Console.WriteLine(big) выведет не числа, а имя внутреннего типа. Поэтому в примерах элементы склеивает string.Join.
Цепочка методов
Методы LINQ ставят друг за другом, как станции на одной ленте. Каждый получает то, что отдал предыдущий:
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); // 3Разберём цепочку:
Where(n => n % 2 == 0)оставляет чётные числа:2 8 4.OrderBy(n => n)сортирует их:2 4 8.Select(n => n * 10)умножает каждое на10:20 40 80.numsне изменился: LINQ не трогает исходный список, а строит новую последовательность.result.ToList()собирает элементы запроса в новыйList<int>. Чтобы собрать их в массив, есть такой же методToArray().
Порядок станций важен. Если поставить Select первым, Where проверял бы уже умноженные числа.
Здесь конвейер и LINQ расходятся. На фабрике станция меняет саму коробку, а метод LINQ исходный список не трогает: он отдаёт дальше новые значения.
Частая ошибка
Ждали, что Where уберёт лишние элементы из самого списка:
List<int> nums = [5, 2, 8, 1];
nums.Where(n => n > 3); // ✗
Console.WriteLine(string.Join(" ", nums)); // 5 2 8 1Программа компилируется и печатает 5 2 8 1: список не изменился. Where возвращает новую последовательность, а здесь её никуда не сохранили, и компилятор об этом не предупреждает.
Сохраните результат: var big = nums.Where(n => n > 3);, а если нужен список — List<int> big = nums.Where(n => n > 3).ToList();. Удалить элементы из самого списка умеет его метод RemoveAll.
Главное
- LINQ — методы для последовательностей; программа .NET 10 подключает
System.Linqсама. Whereотбирает элементы по условию,Selectпревращает каждый элемент в новое значение,OrderByиOrderByDescendingсортируют по ключу.- Методы LINQ не меняют исходный список, а возвращают новую последовательность;
ToList()иToArray()собирают её в список или массив.