JavaRush /Курсы /C# SELF /Лямбда-выражения в коллекциях и

Лямбда-выражения в коллекциях и LINQ

C# SELF
49 уровень , 2 лекция
Открыта

1. Введение

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

Почти все современные коллекции в .NET поддерживают функциональные методы — такие как Where, Select, Find, Any, All и прочие. Их сила — в универсальности и лаконичном стиле: вы просто передаёте "кусочек логики" в виде лямбда-выражения, и коллекция оживает, как будто вы завели новый мотор.

LINQ (Language Integrated Query) — это не просто синтаксический сахар, а целый мини-язык внутри C#, позволяющий писать запросы к данным так, будто вы используете SQL или Excel. Только лучше: прямо в коде, с автодополнением, типами и отладчиком.

Но вся эта магия работает благодаря делегатам — а каждый раз писать отдельный метод ради фильтрации массива довольно утомительно. И вот тут лямбда-выражения вступают в игру как мини-функции "на месте", превращая тяжёлый код в элегантный и выразительный.

2. Лямбда-выражения в стандартных методах коллекций

Лямбда-выражения особенно хорошо проявляют себя в стандартных методах коллекций, основанных на делегатах, таких как Find, Exists, ForEach и многих других.

Пример: Поиск по условию

Допустим, у вас есть список товаров:

using System;
using System.Collections.Generic;

// Наш класс товара
public class Product
{
    public string Name { get; set; }
    public int Price { get; set; }
}

var products = new List<Product>
{
    new Product { Name = "Кофе", Price = 100 },
    new Product { Name = "Чай", Price = 70 },
    new Product { Name = "Молоко", Price = 80 }
};

// Найдём первый дорогой продукт (>90)
Product expensive = products.Find(p => p.Price > 90); // Используем лямбду!
Console.WriteLine(expensive?.Name); // => Кофе

Без лямбды вам бы пришлось писать отдельный метод или анонимную функцию старого стиля. А так — одна строчка, и код читается как английский: "Найди продукт, где цена больше 90".

Пример: Проверка наличия товара

bool hasCheap = products.Exists(p => p.Price < 75);
Console.WriteLine(hasCheap); // => True (ибо "Чай" дешевле 75)

Пример: Обработка всех элементов (ForEach)

Иногда надо что-то сделать с каждым элементом:

products.ForEach(p => Console.WriteLine($"{p.Name}: {p.Price} евро"));

Про аналогии

Если коротко: лямбда-выражения в коллекциях — это как кнопка "сделать красиво" в фоторедакторе. Нажал — и результат!

3. Лямбда-выражения и LINQ: магия для коллекций

LINQ — это не просто удобство, это ещё и отличное вступление в функциональный стиль программирования. Большинство LINQ-методов ожидают на вход делегаты — а значит, их идеальный друг и собутыльник — лямбда-выражения.

Фильтрация с Where

Пусть у нас снова есть список продуктов. Теперь попробуем отобрать только "дешёвые" товары:

using System.Linq;

var cheapProducts = products.Where(p => p.Price < 90);

foreach (var p in cheapProducts)
    Console.WriteLine(p.Name); // Чай, Молоко

Получили новую коллекцию, не написав ни одного цикла вручную. Where принимает лямбду-предикат (функцию, возвращающую true/false), и применяет её к каждому элементу.

Сортировка с OrderBy

Кто любит порядок — тому сюда:

var sorted = products.OrderBy(p => p.Price);

foreach (var p in sorted)
    Console.WriteLine($"{p.Name}: {p.Price}");
// Чай: 70
// Молоко: 80
// Кофе: 100

Маппинг (Select) – проекция данных

Иногда нам нужно не весь объект, а только его часть, например, список названий товаров:

var names = products.Select(p => p.Name);

foreach (var name in names)
    Console.WriteLine(name); // Кофе, Чай, Молоко

Цепочки LINQ

LINQ хорош тем, что можно "цеплять" вызовы один за другим:

var namesOfCheap = products
    .Where(p => p.Price < 90)
    .OrderBy(p => p.Name)
    .Select(p => p.Name.ToUpper());

foreach (var name in namesOfCheap)
    Console.WriteLine(name); // МОЛОКО, ЧАЙ

Похоже на цепочку производственных линий: каждый метод — новая стадия обработки.

Вопрос: Чем лямбда-выражения лучше обычных методов для LINQ?

Во-первых, лямбды можно писать прямо в том месте, где они нужны. Во-вторых, лямбда-выражения короткие и читаемые. В-третьих, это стандарт современного C# — так пишут все, а те, кто не пишет, обычно на собеседования не проходят.

4. Практический пример

В течение курса мы писали учебное приложение для работы с небольшим каталогом товаров, пользователей или заказов. Давайте добавим в него современные методы обработки коллекций.

Поиск пользователя по имени

public class User
{
    public string Username { get; set; }
    public int Age { get; set; }
}

var users = new List<User>
{
    new User{ Username = "Alice", Age = 21 },
    new User{ Username = "Bob", Age = 26 },
    new User{ Username = "Charlie", Age = 32 }
};

// Поиск пользователя по имени
User found = users.FirstOrDefault(u => u.Username == "Bob");
Console.WriteLine(found?.Age); // 26

Фильтрация по возрасту

var adults = users.Where(u => u.Age >= 18);

foreach (var u in adults)
    Console.WriteLine(u.Username); // Alice, Bob, Charlie

Подсчёт количества юзеров

int count = users.Count(u => u.Age > 25);
Console.WriteLine(count); // 2 (Bob и Charlie)

Проверка всех юзеров на совершеннолетие

bool allAdults = users.All(u => u.Age >= 18);
Console.WriteLine(allAdults); // True

Есть ли хоть один несовершеннолетний?

bool hasMinor = users.Any(u => u.Age < 18);
Console.WriteLine(hasMinor); // False

5. LINQ: Как это всё работает изнутри

Когда вы пишете, например, Where(u => u.Age > 20), на самом деле это примерно то же, что создать цикл, который перебирает все элементы и проверяет для каждого условие. Только LINQ делает это незаметно и красиво, оборачивая ваш предикат в делегат.

Ведь, если бы не лямбда-выражения, пришлось бы городить такие конструкции:

public static bool AgeMoreThan20(User u) => u.Age > 20;
var adultUsers = users.Where(AgeMoreThan20);

Или вообще анонимные методы в "олдскульном" стиле:

var adultUsers = users.Where(delegate(User u) { return u.Age > 20; });

Всё это громоздко и уныло. С лямбдой — изящно и современно.

6. Делегаты и стандартные типы: Func, Action, Predicate

Не только LINQ обожает лямбда-выражения. Многие методы стандартных коллекций принимают специализированные делегаты, например:

  • Predicate<T> — для методов Find, Exists, RemoveAll
  • Func<T, TResult> — для LINQ-методов, проекций, вычислений
  • Action<T> — для методов, которые делают что-то с элементом, но ничего не возвращают (ForEach)

Вот как это выглядит на практике:

// Predicate<T>
users.RemoveAll(u => u.Age < 30); // Удалили всех моложе 30

// Func<T, TResult>
var names = users.Select(u => u.Username);

// Action<T>
users.ForEach(u => Console.WriteLine(u.Username));

7. Шпаргалка по методам коллекций с лямбда-выражениями

Метод Что делает Тип делегата Пример лямбды
Where
Фильтрует элементы
Func<T, bool>
p => p.Price > 100
Select
Проецирует, преобразует
Func<T, U>
p => p.Name
OrderBy
Сортировка по ключу
Func<T, K>
u => u.Age
FirstOrDefault
Первый элемент по условию
Func<T, bool>
u => u.Username == "Bob"
Any
Есть ли хоть один элемент по условию
Func<T, bool>
u => u.Age < 18
All
Все ли элементы удовлетворяют условию
Func<T, bool>
u => u.Age >= 18
Count
Кол-во элементов по условию
Func<T, bool>
p => p.Price > 50
ForEach
Для каждого элемента что-то сделать
Action<T>
u => Console.WriteLine(u.Name)
RemoveAll
Удаляет все по предикату
Predicate<T>
u => u.Age < 18

8. Типичные ошибки и особенности

Одна из самых частых ошибок — забыть, что LINQ не изменяет исходную коллекцию, а возвращает новую последовательность. То есть, после кода var sorted = users.OrderBy(u => u.Age); коллекция users останется в исходном порядке! Это может сбить с толку: иногда кажется, что всё уже отсортировано — а на самом деле нет.

Ещё нюанс: методы типа Where, Select и другие возвращают объекты типа IEnumerable<T>. Это "ленивая" коллекция — реальная обработка начинается, когда вы реально начинаете что-то перечислять (foreach, ToList() и т.д.). Поэтому если хотите материализовать результат, не забудьте вызвать ToList() или ToArray():

var sortedList = users.OrderBy(u => u.Age).ToList();

Также помним: если лямбда-выражение обращается к переменным вне своей области видимости (замыкания), то эти переменные продолжают "жить" в памяти до тех пор, пока жива ссылка на лямбду. Не очень страшно, но если вы используете лямбду внутри долгоживущего объекта и захватили в ней "огромный массив", то массив будет висеть в памяти вместе с лямбдой.

И ещё: используйте говорящие имена параметров и переменных — это сильно повышает читабельность, особенно если у вас несколько уровней вложенных лямбд.

2
Задача
C# SELF, 49 уровень, 2 лекция
Недоступна
Фильтрация списка с использованием лямбда-выражений
Фильтрация списка с использованием лямбда-выражений
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ