C# Курс

C# LINQ: запросы к коллекциям

Самоучитель Python 3, собранный из материалов данного сайта.Предназначен в основном для тех, кто хочет изучить язык программирования Python с нуля.

Урок 6: Знакомство с LINQ — волшебная палочка для работы с данными

Введение: Представь, что ты сортируешь коллекцию марок

Представь, что у тебя есть огромная коллекция марок. Ты хочешь найти только те, которые:

  • Выпущены после 2010 года
  • Стоят дороже 100 рублей
  • Отсортировать их по стране

Без специальных инструментов тебе пришлось бы перебирать каждую марку вручную, проверять условия, записывать результаты... Это долго и скучно! В программировании мы часто сталкиваемся с похожей задачей — нужно обработать коллекции данных. И тут на помощь приходит LINQ (Language Integrated Query) — это как волшебная палочка, которая позволяет писать запросы к данным прямо внутри C#.

Простыми словами: LINQ — это способ задать вопрос "что я хочу получить из этого списка?" вместо того, чтобы объяснять компьютеру "как именно это сделать".

Что такое LINQ и зачем он нужен?

LINQ (произносится "линк") — это набор методов, которые встроены прямо в C#. Они позволяют работать с любыми коллекциями: массивами, списками, словарями, базами данных и даже XML-файлами.

Главное преимущество: код становится короче, понятнее и безопаснее. Вместо 10 строк с циклами и условиями ты пишешь 2-3 строки запроса.

Давай сравним:

Без LINQ (старый способ):

// Дан список чисел, нужно найти все четные
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> evenNumbers = new List<int>();

foreach (int num in numbers)
{
    if (num % 2 == 0) // проверяем, четное ли число
    {
        evenNumbers.Add(num);
    }
}

// Выводим результат
Console.WriteLine("Четные числа:");
foreach (int num in evenNumbers)
{
    Console.Write(num + " ");
}
// Результат: 2 4 6 8 10

С LINX (новый, крутой способ):

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Одна строка — и готово!
var evenNumbers = numbers.Where(n => n % 2 == 0);

Console.WriteLine("Четные числа: " + string.Join(" ", evenNumbers));
// Результат: 2 4 6 8 10

Видишь разницу? В LINQ мы просто говорим: "Где число четное?" — и получаем результат. Компьютер сам решает, как это сделать.

Два способа писать LINQ: синтаксис запросов и синтаксис методов

У LINQ есть два стиля. Они делают одно и то же, но выглядят по-разному. Выбирай тот, который тебе понятнее!

Способ 1: Синтаксис запросов (похож на SQL)

Этот стиль напоминает язык запросов к базам данных SQL. Ты пишешь "from... where... select".

// Пример: найти все числа больше 5 и отсортировать по возрастанию
List<int> numbers = new List<int> { 10, 3, 7, 1, 8, 2 };

var result = from num in numbers
             where num > 5
             orderby num
             select num;

Console.WriteLine("Числа больше 5: " + string.Join(", ", result));
// Результат: 7, 8, 10

Разберем по частям:

  • from num in numbers — берем каждый элемент из списка numbers и называем его num
  • where num > 5 — оставляем только те, где условие истинно
  • orderby num — сортируем по возрастанию
  • select num — выбираем, что хотим получить (в данном случае само число)

Способ 2: Синтаксис методов (цепочка вызовов)

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

// Тот же пример, но в стиле методов
List<int> numbers = new List<int> { 10, 3, 7, 1, 8, 2 };

var result = numbers
    .Where(num => num > 5)   // фильтруем
    .OrderBy(num => num);     // сортируем

Console.WriteLine("Числа больше 5: " + string.Join(", ", result));
// Результат: 7, 8, 10

Обрати внимание на => — это лямбда-выражение. Оно означает "для каждого num выполни действие". num => num > 5 читается как "num такой, что num больше 5".

Основные методы LINQ

Давай познакомимся с самыми популярными методами. Их не так много, но они очень мощные!

1. Where — фильтрация (оставить только те, что подходят)

Аналогия из жизни: Ты в магазине и хочешь купить только красные яблоки. Ты берешь корзину (коллекцию) и отбираешь только красные.

// Список студентов с оценками
var students = new List<(string Name, int Grade)>
{
    ("Аня", 85),
    ("Петя", 92),
    ("Маша", 78),
    ("Вася", 95),
    ("Катя", 88)
};

// Найти всех, у кого оценка выше 85
var excellentStudents = students.Where(s => s.Grade > 85);

Console.WriteLine("Студенты с оценкой выше 85:");
foreach (var student in excellentStudents)
{
    Console.WriteLine($"{student.Name}: {student.Grade}");
}
// Результат:
// Петя: 92
// Вася: 95
// Катя: 88

2. Select — преобразование (создать что-то новое из каждого элемента)

Аналогия из жизни: У тебя есть список друзей с их днями рождения. Ты хочешь получить только имена, без дат.

// Список чисел
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// Получить квадраты чисел
var squares = numbers.Select(n => n * n);

Console.WriteLine("Квадраты чисел: " + string.Join(", ", squares));
// Результат: 1, 4, 9, 16, 25

// Можно преобразовать в другой тип
var descriptions = numbers.Select(n => $"Число {n}");

foreach (var desc in descriptions)
{
    Console.WriteLine(desc);
}
// Результат:
// Число 1
// Число 2
// Число 3
// Число 4
// Число 5

3. OrderBy / OrderByDescending — сортировка

Аналогия из жизни: Ты расставляешь книги на полке по алфавиту или по году издания.

// Список фруктов
List<string> fruits = new List<string> { "Банан", "Апельсин", "Яблоко", "Киви", "Груша" };

// Сортировка по алфавиту (по возрастанию)
var sortedAsc = fruits.OrderBy(f => f);
Console.WriteLine("По алфавиту: " + string.Join(", ", sortedAsc));
// Результат: Апельсин, Банан, Груша, Киви, Яблоко

// Сортировка по убыванию
var sortedDesc = fruits.OrderByDescending(f => f);
Console.WriteLine("В обратном порядке: " + string.Join(", ", sortedDesc));
// Результат: Яблоко, Киви, Груша, Банан, Апельсин

4. First / FirstOrDefault — взять первый элемент

Аналогия из жизни: Ты ищешь первую книгу на полке, которая начинается на букву "А".

List<int> numbers = new List<int> { 10, 20, 30, 40, 50 };

// Взять первый элемент
int first = numbers.First();
Console.WriteLine($"Первый элемент: {first}"); // 10

// Взять первый элемент, который удовлетворяет условию
int firstBig = numbers.First(n => n > 25);
Console.WriteLine($"Первое число больше 25: {firstBig}"); // 30

// Если ничего не найдено, First вызовет ошибку!
// FirstOrDefault вернет 0 (значение по умолчанию для int)
int notFound = numbers.FirstOrDefault(n => n > 100);
Console.WriteLine($"Число больше 100: {notFound}"); // 0

5. Count — подсчет количества

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Сколько четных чисел?
int evenCount = numbers.Count(n => n % 2 == 0);
Console.WriteLine($"Четных чисел: {evenCount}"); // 5

// Сколько чисел больше 5?
int bigCount = numbers.Count(n => n > 5);
Console.WriteLine($"Чисел больше 5: {bigCount}"); // 5

6. Any / All — проверка условий

Аналогия из жизни: Ты проверяешь, есть ли в корзине хотя бы одно зеленое яблоко (Any) или все яблоки красные (All).

List<int> numbers = new List<int> { 2, 4, 6, 8, 10 };

// Есть ли хотя бы одно нечетное?
bool hasOdd = numbers.Any(n => n % 2 != 0);
Console.WriteLine($"Есть нечетные? {hasOdd}"); // False

// Все ли числа четные?
bool allEven = numbers.All(n => n % 2 == 0);
Console.WriteLine($"Все четные? {allEven}"); // True

7. Distinct — убрать дубликаты

List<int> numbers = new List<int> { 1, 2, 2, 3, 3, 3, 4, 5, 5 };

var uniqueNumbers = numbers.Distinct();
Console.WriteLine("Уникальные числа: " + string.Join(", ", uniqueNumbers));
// Результат: 1, 2, 3, 4, 5

8. Take — взять первые N элементов

List<int> numbers = new List<int> { 10, 20, 30, 40, 50, 60 };

// Взять первые 3
var top3 = numbers.Take(3);
Console.WriteLine("Первые 3: " + string.Join(", ", top3));
// Результат: 10, 20, 30

9. Skip — пропустить первые N элементов

// Пропустить первые 2, взять остальные
var afterFirst2 = numbers.Skip(2);
Console.WriteLine("После первых 2: " + string.Join(", ", afterFirst2));
// Результат: 30, 40, 50, 60

Комбинируем методы — магия LINQ

Самое крутое, что методы можно объединять в цепочки! Давай решим настоящую задачу:

Задача: У нас есть список товаров в интернет-магазине. Нужно найти 3 самых дешевых товара, которые есть в наличии, и вывести их названия.

// Создаем класс товара
public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public bool InStock { get; set; }
}

// Заполняем список
var products = new List<Product>
{
    new Product { Name = "Ноутбук", Price = 50000, InStock = true },
    new Product { Name = "Мышь", Price = 1500, InStock = false },
    new Product { Name = "Клавиатура", Price = 3000, InStock = true },
    new Product { Name = "Монитор", Price = 15000, InStock = true },
    new Product { Name = "Наушники", Price = 2500, InStock = true },
    new Product { Name = "Коврик", Price = 500, InStock = false }
};

// Решаем задачу одной цепочкой!
var cheapItems = products
    .Where(p => p.InStock)              // только те, что в наличии
    .OrderBy(p => p.Price)              // сортируем по цене (от дешевых к дорогим)
    .Take(3)                            // берем первые 3 (самые дешевые)
    .Select(p => p.Name);              // берем только названия

Console.WriteLine("Самые дешевые товары в наличии:");
foreach (var name in cheapItems)
{
    Console.WriteLine($"- {name}");
}
// Результат:
// - Клавиатура
// - Наушники
// - Монитор

Обрати внимание: мы не использовали ни одного цикла! Код читается как книга: "Из товаров возьми те, что в наличии, отсортируй по цене, возьми первые три и выбери названия".

Еще пример: работа со строками

// Список имен
List<string> names = new List<string> 
{ 
    "Александр", 
    "Мария", 
    "Дмитрий", 
    "Елена", 
    "Иван", 
    "Ольга",
    "Анна"
};

// Найти все имена длиннее 5 букв, отсортировать по длине
var longNames = names
    .Where(n => n.Length > 5)
    .OrderBy(n => n.Length)
    .ThenBy(n => n); // если длина одинаковая, сортируем по алфавиту

Console.WriteLine("Имена длиннее 5 букв (по возрастанию длины):");
foreach (var name in longNames)
{
    Console.WriteLine($"{name} ({name.Length} букв)");
}
// Результат:
// Мария (5 букв) — не войдет, так как 5 не больше 5
// Елена (5 букв) — тоже не войдет
// Анна (4 буквы) — не войдет
// Дмитрий (7 букв)
// Александр (9 букв)

Группировка данных с GroupBy

Аналогия из жизни: Ты раскладываешь носки по цветам: все синие в одну кучку, все красные — в другую.

// Список учеников с классами
var students = new List<(string Name, string Class)>
{
    ("Петя", "5А"),
    ("Вася", "5Б"),
    ("Маша", "5А"),
    ("Катя", "5Б"),
    ("Дима", "5А"),
    ("Оля", "5В")
};

// Группируем по классу
var groups = students.GroupBy(s => s.Class);

foreach (var group in groups)
{
    Console.WriteLine($"Класс {group.Key}:");
    foreach (var student in group)
    {
        Console.WriteLine($"  - {student.Name}");
    }
    Console.WriteLine($"  Всего: {group.Count()} учеников\n");
}
// Результат:
// Класс 5А:
//   - Петя
//   - Маша
//   - Дима
//   Всего: 3 ученика
// 
// Класс 5Б:
//   - Вася
//   - Катя
//   Всего: 2 ученика
// 
// Класс 5В:
//   - Оля
//   Всего: 1 ученик

Агрегатные функции: Sum, Min, Max, Average

LINQ умеет считать сумму, минимум, максимум и среднее — очень удобно для чисел!

List<int> numbers = new List<int> { 10, 20, 30, 40, 50 };

int sum = numbers.Sum();        // сумма: 150
int min = numbers.Min();        // минимум: 10
int max = numbers.Max();        // максимум: 50
double avg = numbers.Average(); // среднее: 30

Console.WriteLine($"Сумма: {sum}");
Console.WriteLine($"Минимум: {min}");
Console.WriteLine($"Максимум: {max}");
Console.WriteLine($"Среднее: {avg}");

// Можно применять к свойствам объектов
var products = new List<Product>
{
    new Product { Name = "Товар1", Price = 100 },
    new Product { Name = "Товар2", Price = 200 },
    new Product { Name = "Товар3", Price = 150 }
};

decimal totalPrice = products.Sum(p => p.Price);
Console.WriteLine($"Общая стоимость: {totalPrice}"); // 450

Ленивые вычисления (Deferred Execution)

Важная особенность LINQ! Когда ты пишешь запрос, он не выполняется сразу. Он выполняется только когда ты начинаешь перебирать результаты (в foreach) или вызываешь методы вроде ToList(), Count(), First().

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// Создаем запрос
var bigNumbers = numbers.Where(n => n > 3);

// В этот момент запрос еще не выполнен!
numbers.Add(6); // Добавляем число ПОСЛЕ создания запроса

// А теперь перебираем — запрос выполнится с учетом нового числа!
Console.WriteLine("Числа больше 3:");
foreach (var n in bigNumbers)
{
    Console.WriteLine(n);
}
// Результат: 4, 5, 6 (6 тоже попала, хотя добавили после!)

Чтобы "заморозить" результат, используй ToList() или ToArray():

var frozenNumbers = numbers.Where(n => n > 3).ToList();
// Теперь результат зафиксирован, изменения в numbers не повлияют

Практические задания

А теперь попробуй сам! Вот три задания разной сложности. Постарайся использовать LINQ без циклов.

Задание 1 (Легкое): Работа с числами

У тебя есть список чисел: [5, 12, 8, 3, 15, 7, 10, 6, 9, 4].

  • Найди все четные числа
  • Отсортируй их по убыванию
  • Возьми первые 3
  • Выведи их квадраты

Подсказка: Используй Where, OrderByDescending, Take, Select.

Задание 2 (Среднее): Анализ оценок учеников

Создай список учеников с именами и оценками (от 1 до 10). Например:

var students = new List<(string Name, int Grade)>
{
    ("Аня", 8),
    ("Петя", 5),
    ("Маша", 9),
    ("Вася", 4),
    ("Катя", 7),
    ("Дима", 6),
    ("Оля", 10)
};
  • Выведи имена учеников с оценкой выше 6
  • Посчитай среднюю оценку
  • Найди ученика с самой высокой оценкой (используй First или FirstOrDefault с OrderByDescending)
  • Сгруппируй учеников по оценкам (кто получил 5, кто 6 и т.д.)

Задание 3 (Сложное): Каталог книг

Создай класс Book с полями Title (название), Author (автор), Year (год издания), Pages (количество страниц). Создай список из 6-8 книг.

  • Найди все книги, написанные после 2000 года
  • Отсортируй их по году издания (от новых к старым)
  • Выбери только названия и авторов
  • Посчитай общее количество страниц среди книг, у которых больше 300 страниц
  • Найди самую толстую книгу (максимум страниц)

Пример для начала:

public class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public int Year { get; set; }
    public int Pages { get; set; }
}

// Создай список книг и выполни задания

Заключение

LINQ — это не просто набор методов. Это способ мышления. Вместо того чтобы писать пошаговые инструкции (как делать), ты описываешь желаемый результат (что получить). Это делает код чище, понятнее и безопаснее.

Помни главное:

  • Where — фильтрует (оставляет подходящие)
  • Select — преобразует (создает новое)
  • OrderBy — сортирует
  • Take/Skip — берет часть
  • First/FirstOrDefault — берет первый
  • Count — считает
  • Any/All — проверяет условия
  • GroupBy — группирует
  • Sum/Min/Max/Average — считает статистику

Попробуй решить задания, и ты увидишь, как LINX упрощает жизнь программиста. Удачи!