Перейти к содержимому
Теория · Лямбды и LINQ · урок 6 из 10

LINQ: Where, Select, OrderBy

Представьте конвейер на фабрике. Коробки едут по ленте мимо станций. Одна станция снимает коробки с браком, другая перекладывает содержимое в новую упаковку, третья расставляет коробки по размеру. Каждая станция делает одно дело, а вместе они превращают поток коробок в то, что нужно.

Со списком в программе часто нужно то же самое. Допустим, из списка надо взять числа больше трёх. Циклом это восемь строк: новый список, foreach, if, Add и фигурные скобки. А сама мысль умещается в четыре слова: «оставь числа больше трёх».

Для таких задач в .NET есть LINQ — набор методов, которые отбирают, преобразуют и сортируют элементы последовательности: списка, массива — всего, что можно перебрать в foreach. Методы LINQ лежат в пространстве имён System.Linq, и программа .NET 10 подключает его сама, как и System.

C#
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 сортирует по убыванию. Ключ выбираете вы:

C#
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 ставят друг за другом, как станции на одной ленте. Каждый получает то, что отдал предыдущий:

C#
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

Разберём цепочку:

  1. Where(n => n % 2 == 0) оставляет чётные числа: 2 8 4.
  2. OrderBy(n => n) сортирует их: 2 4 8.
  3. Select(n => n * 10) умножает каждое на 10: 20 40 80.
  4. nums не изменился: LINQ не трогает исходный список, а строит новую последовательность.
  5. result.ToList() собирает элементы запроса в новый List<int>. Чтобы собрать их в массив, есть такой же метод ToArray().

Порядок станций важен. Если поставить Select первым, Where проверял бы уже умноженные числа.

Здесь конвейер и LINQ расходятся. На фабрике станция меняет саму коробку, а метод LINQ исходный список не трогает: он отдаёт дальше новые значения.

Частая ошибка

Ждали, что Where уберёт лишние элементы из самого списка:

C#
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() собирают её в список или массив.