1. Базовые способы фильтрации коллекций
Фильтрация — это как сито для золота, только вместо самородков мы отбираем из коллекции только те элементы, которые нам нужны. Например, есть у нас список пользователей — хочется найти только совершеннолетних, или только студентов, или только тех, кто любит кофе (программистов, короче). Выборка подходящих элементов — это очень частая задача, которая встречается везде: от работы с БД до обработки пользовательского ввода.
Давайте рассмотрим разные способы фильтрации в C#. По мере изучения будем развивать наше учебное консольное приложение, добавляя в него фильтры.
Ручная фильтрация с помощью цикла
Начнем с самого простого и классического способа: фильтрации с помощью foreach:
// Пример: есть список чисел, нужно выбрать только чётные
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = new List<int>();
foreach (int number in numbers)
{
if (number % 2 == 0) // если число чётное
{
evenNumbers.Add(number);
}
}
Console.WriteLine("Чётные числа:");
foreach (int n in evenNumbers)
{
Console.WriteLine(n);
}
Такой способ работает всегда, его легко понять, но он не самый компактный и не самый "современный".
Почему ручная фильтрация не всегда удобна?
Когда у вас появляется другая коллекция, другой критерий фильтрации, или вам нужно писать сразу несколько фильтров — получится много похожего кода. Хочется компактности, читаемости и модульности.
2. Фильтрация по сложным критериям
Допустим, у нас есть коллекция объектов. Возьмём пример с пользователями из нашего развиваемого приложения.
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsStudent { get; set; }
}
Объявляем список пользователей:
List<User> users = new List<User>
{
new User { Name = "Аня", Age = 17, IsStudent = true },
new User { Name = "Борис", Age = 21, IsStudent = false },
new User { Name = "Вика", Age = 19, IsStudent = true },
new User { Name = "Глеб", Age = 25, IsStudent = false }
};
Пример 1: Выбрать всех студентов
List<User> students = new List<User>();
foreach (User user in users)
{
if (user.IsStudent)
students.Add(user);
}
Console.WriteLine("Список студентов:");
foreach (User user in students)
{
Console.WriteLine($"{user.Name} ({user.Age})");
}
Пример 2: Выбрать совершеннолетних студентов
List<User> adults = new List<User>();
foreach (User user in users)
{
if (user.Age >= 18 && user.IsStudent)
adults.Add(user);
}
Console.WriteLine("Совершеннолетние студенты:");
foreach (User user in adults)
{
Console.WriteLine($"{user.Name} ({user.Age})");
}
А если критерии фильтрации динамические?
Можно выносить условия фильтрации в отдельные методы, чтобы вызывать их внутри цикла:
bool IsAdult(User u) { return u.Age >= 18; }
bool IsStudent(User u) { return u.IsStudent; }
List<User> filtered = new List<User>();
foreach (User user in users)
{
if (IsAdult(user) && IsStudent(user))
filtered.Add(user);
}
3. Фильтрация по ключу в словарях
Списки и массивы — это удобно, но часто мы работаем и со словарями.
Допустим, у нас есть словарь, где ключ — имя пользователя, а значение — его возраст:
Dictionary<string, int> ageByName = new Dictionary<string, int>
{
["Аня"] = 17,
["Борис"] = 21,
["Вика"] = 19,
["Глеб"] = 25
};
Задача: выбрать только пользователей 18+
Dictionary<string, int> adults = new Dictionary<string, int>();
foreach (var pair in ageByName)
{
if (pair.Value >= 18)
adults.Add(pair.Key, pair.Value);
}
Console.WriteLine("Совершеннолетние:");
foreach (var pair in adults)
{
Console.WriteLine($"{pair.Key}: {pair.Value} лет");
}
Задача: выбрать пользователей с именами на букву "В"
Dictionary<string, int> namesWithV = new Dictionary<string, int>();
foreach (var pair in ageByName)
{
if (pair.Key.StartsWith("В"))
namesWithV.Add(pair.Key, pair.Value);
}
Console.WriteLine("Имёна на букву 'В':");
foreach (var pair in namesWithV)
{
Console.WriteLine($"{pair.Key}: {pair.Value} лет");
}
4. Принципы фильтрации
Как работает процесс фильтрации
┌──────────────────────────────────┐
│ Список: [1, 2, 3, 4, 5, 6] │
└──────────────────────────────────┘
│
▼
[Проверка: n % 2 == 0]
│
▼
┌──────────────────────────────────┐
│ Результат: [2, 4, 6] │
└──────────────────────────────────┘
Композиция фильтров: фильтруем по разным условиям
Можно делать последовательные фильтрации, если хочется поэтапно отбирать данные:
// Сначала фильтруем только студентов
List<User> onlyStudents = new List<User>();
foreach (User user in users)
{
if (user.IsStudent)
onlyStudents.Add(user);
}
// Потом выбираем среди них только совершеннолетних
List<User> adultStudents = new List<User>();
foreach (User user in onlyStudents)
{
if (user.Age >= 18)
adultStudents.Add(user);
}
Или всё сразу:
List<User> adultStudents = new List<User>();
foreach (User user in users)
{
if (user.IsStudent && user.Age >= 18)
adultStudents.Add(user);
}
Сортировка и другие операции
Сортировку можно делать с помощью метода Sort для списков:
// Совершеннолетние студенты, отсортированные по возрасту
adultStudents.Sort((a, b) => a.Age.CompareTo(b.Age));
Подробнее о сортировке поговорим на следующей лекции!
5. Фильтрация в пользовательских сценариях
1. Фильтрация ввода пользователя
Допустим, наше приложение принимает список оценок и должно выбрать все "пятёрки".
Console.Write("Введите оценки через пробел: ");
string input = Console.ReadLine();
List<int> grades = new List<int>();
foreach (string s in input.Split(' '))
{
if (int.TryParse(s, out int grade))
grades.Add(grade);
}
List<int> fives = new List<int>();
foreach (int grade in grades)
{
if (grade == 5)
fives.Add(grade);
}
Console.WriteLine("Пятёрки:");
foreach (var grade in fives)
{
Console.WriteLine(grade);
}
2. Фильтрация по множеству условий
Задача: выбрать пользователей-студентов в возрасте от 18 до 22 лет включительно.
List<User> filtered = new List<User>();
foreach (User user in users)
{
if (user.IsStudent && user.Age >= 18 && user.Age <= 22)
filtered.Add(user);
}
3. Фильтрация по наличию элемента
Пусть у нас есть список строк, и мы хотим выбрать только те, которые содержат подстроку ".net" (без учёта регистра).
List<string> technologies = new List<string> { "C#", ".NET", "Java", "dotnet", "JavaScript" };
List<string> netTechs = new List<string>();
foreach (var tech in technologies)
{
if (tech.ToLower().Contains(".net"))
netTechs.Add(tech);
}
foreach (var tech in netTechs)
{
Console.WriteLine(tech);
}
6. Особенности и типичные ошибки при фильтрации
Фильтрация кажется простой, но здесь можно наломать дров. Давайте разберём некоторые особенности.
Изменение исходной коллекции
Если вы фильтруете исходную коллекцию, а затем меняете её содержимое после фильтрации, — результат не изменится, если вы уже создали новый список. Если же вы только запомнили ссылку на старый список, изменения в исходной коллекции могут повлиять на итог.
List<int> filtered = new List<int>();
foreach (int n in numbers)
{
if (n > 2)
filtered.Add(n);
}
// Внесём изменение в исходный список
numbers.Add(10);
foreach (var n in filtered)
{
Console.WriteLine(n); // 10 не попадёт сюда
}
Результат ручной фильтрации
Результатом ручной фильтрации обычно является новый изменяемый список (например, List<T>), с которым можно делать всё, что нужно — добавлять, удалять элементы и т.д.
Фильтрация и производительность
Фильтры в циклах вызываются для каждого элемента коллекции, так что если функция внутри условия тяжёлая — будьте осторожны. Иногда проще сделать простой цикл foreach, особенно если есть сложная логика и требуется логирование или дополнительные действия.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ