🔷 C#

Методы массивов и коллекций в C# (.NET): подробное руководство

Что это такое и зачем? (аналогия из реальной жизни)

Представьте, что вы — библиотекарь. У вас есть огромный стеллаж с книгами. Каждая книга — это отдельный элемент данных (число, строка, объект). Сам стеллаж — это массив или коллекция.

Раньше, чтобы найти книгу по названию, вам приходилось вручную перебирать каждую полку, брать каждую книгу, читать её название и класть обратно. Это долго, скучно и легко ошибиться. А если нужно отсортировать все книги по году издания? Или найти самую толстую? Или удалить все книги с рваными страницами? Без специальных инструментов это превращается в настоящий кошмар.

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

В программировании эти методы позволяют нам не писать циклы вручную для каждой простой операции. Вместо того чтобы писать 10 строк кода для поиска элемента, мы пишем одну строку с методом Find. Вместо того чтобы создавать новый массив и вручную копировать в него отфильтрованные элементы, мы используем Where. Это делает код короче, понятнее и безопаснее.

Важно: Методы массивов и коллекций — это не просто «синтаксический сахар». Они оптимизированы профессионалами Microsoft, работают быстрее ручных циклов во многих сценариях и содержат встроенную обработку ошибок.

Теория: как это работает изнутри

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

Делегаты — это «контейнеры для функций»

Представьте, что вы хотите, чтобы ваш робот-библиотекарь мог выполнять разные команды: «найди книгу», «отсортируй», «удали». Вместо того чтобы вшивать каждую команду в робота, вы даёте ему слот, в который можно вставить любую команду. Этот слот и есть делегат.

В C# делегат — это тип, который может хранить ссылку на метод. Например, делегат Func<int, bool> может хранить любой метод, который принимает число и возвращает true или false.

Лямбда-выражения — это «функции на лету»

Вместо того чтобы писать отдельный метод для каждой простой операции, мы можем создать функцию «прямо на месте», не давая ей имени. Это и есть лямбда-выражение. Выглядит оно так:

// Обычный метод
bool IsEven(int number) {
    return number % 2 == 0;
}

// То же самое, но лямбда-выражением
(int number) => number % 2 == 0;

Стрелочка => читается как «переходит в» или «возвращает». Всё, что слева — входные параметры, справа — выражение, которое будет вычислено и возвращено.

Как методы обрабатывают коллекции

Большинство методов массивов и коллекций работают по одному принципу:

  1. Берут каждый элемент коллекции по очереди.
  2. Применяют к нему вашу функцию (лямбда-выражение).
  3. В зависимости от метода, либо возвращают результат (например, true/false для Any), либо формируют новую коллекцию (например, для Where).

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

Запомните: Массивы в C# имеют фиксированный размер. Коллекции (List, Dictionary, HashSet) могут расти и сжиматься. Методы для них во многом похожи, но есть важные отличия.

Синтаксис в C# (.NET): все формы и варианты

Рассмотрим основные методы, которые вам понадобятся. Я разделил их на группы по назначению.

1. Методы поиска и проверки

Метод Что делает Пример Результат
Contains Проверяет, есть ли элемент в коллекции list.Contains(5) true или false
Any Проверяет, удовлетворяет ли хотя бы один элемент условию list.Any(x => x > 10) true или false
All Проверяет, удовлетворяют ли ВСЕ элементы условию list.All(x => x > 0) true или false
Find Находит ПЕРВЫЙ элемент, удовлетворяющий условию list.Find(x => x.Name == "Вася") Элемент или null
FindAll Находит ВСЕ элементы, удовлетворяющие условию list.FindAll(x => x.Age > 18) Новый список
IndexOf Находит индекс первого вхождения элемента list.IndexOf("Привет") Число (индекс) или -1

2. Методы преобразования

Метод Что делает Пример Результат
Select Преобразует каждый элемент по заданному правилу list.Select(x => x * 2) Новая коллекция с удвоенными числами
Where Фильтрует элементы по условию list.Where(x => x.Length > 5) Новая коллекция только с длинными строками
OrderBy Сортирует по возрастанию list.OrderBy(x => x.Price) Новая отсортированная коллекция
OrderByDescending Сортирует по убыванию list.OrderByDescending(x => x.Date) Новая отсортированная коллекция
ToList Преобразует результат в список query.ToList() List<T>
ToArray Преобразует результат в массив query.ToArray() T[]

3. Методы агрегации (подсчёта)

Метод Что делает Пример Результат
Count Подсчитывает количество элементов list.Count(x => x.IsActive) Число
Sum Суммирует числовые значения list.Sum(x => x.Salary) Число
Average Вычисляет среднее арифметическое list.Average(x => x.Grade) Число с плавающей точкой
Min Находит минимальное значение list.Min(x => x.Age) Число
Max Находит максимальное значение list.Max(x => x.Height) Число
Aggregate Универсальный метод накопления list.Aggregate((a, b) => a + b) Одно значение (сумма всех)

4. Методы для работы со строками в коллекциях

Метод Что делает Пример Результат
string.Join Соединяет элементы в одну строку через разделитель string.Join(", ", names) "Аня, Вася, Петя"
string.Concat Соединяет элементы без разделителя string.Concat(numbers) "12345"

5. Методы для изменения коллекций (только для List)

Метод Что делает Пример
Add Добавляет элемент в конец list.Add(42)
AddRange Добавляет несколько элементов list.AddRange(new[] {1, 2, 3})
Insert Вставляет элемент по индексу list.Insert(0, "Первый")
Remove Удаляет первое вхождение элемента list.Remove("Вася")
RemoveAt Удаляет элемент по индексу list.RemoveAt(3)
RemoveAll Удаляет все элементы, удовлетворяющие условию list.RemoveAll(x => x < 0)
Sort Сортирует список на месте list.Sort()
Reverse Переворачивает список list.Reverse()
Clear Очищает список list.Clear()
Важное замечание: Методы Sort, Reverse, RemoveAll изменяют исходный список. Методы OrderBy, Where, Select создают НОВУЮ коллекцию, не трогая исходную. Это критически важно помнить, чтобы не получить ошибки в программе.

Примеры кода — от простого к сложному

Пример 1: Базовые методы поиска

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        // Создаём список чисел
        List<int> numbers = new List<int> { 5, 12, 3, 8, 15, 7, 10 };
        
        // 1. Contains — проверяем, есть ли число 8
        bool hasEight = numbers.Contains(8);
        Console.WriteLine($"Есть ли 8? {hasEight}"); // Вывод: Есть ли 8? True
        
        // 2. Any — есть ли хотя бы одно число больше 10?
        bool anyAboveTen = numbers.Any(x => x > 10);
        Console.WriteLine($"Есть числа > 10? {anyAboveTen}"); // Вывод: True
        
        // 3. All — все ли числа положительные?
        bool allPositive = numbers.All(x => x > 0);
        Console.WriteLine($"Все числа > 0? {allPositive}"); // Вывод: True
        
        // 4. Find — найти первое чётное число
        int firstEven = numbers.Find(x => x % 2 == 0);
        Console.WriteLine($"Первое чётное: {firstEven}"); // Вывод: 12
        
        // 5. FindAll — найти все числа больше 5
        List<int> bigNumbers = numbers.FindAll(x => x > 5);
        Console.WriteLine($"Числа > 5: {string.Join(", ", bigNumbers)}"); // Вывод: 12, 8, 15, 7, 10
    }
}

Объяснение: В этом примере мы используем самые простые методы. Обратите внимание, что Find возвращает только первый подходящий элемент, а FindAll — все. Any и All работают как логические вопросы: «есть ли хоть один?» и «все ли такие?».

Пример 2: Преобразование данных (Select и Where)

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        // Создаём список имён
        List<string> names = new List<string> { "аня", "вася", "петя", "маша", "иван" };
        
        // 1. Select — преобразуем все имена в заглавные
        var upperNames = names.Select(name => name.ToUpper());
        Console.WriteLine($"Заглавные: {string.Join(", ", upperNames)}");
        // Вывод: АНЯ, ВАСЯ, ПЕТЯ, МАША, ИВАН
        
        // 2. Where — оставляем только имена длиннее 3 букв
        var longNames = names.Where(name => name.Length > 3);
        Console.WriteLine($"Длинные имена: {string.Join(", ", longNames)}");
        // Вывод: вася, петя, маша, иван
        
        // 3. Комбинируем: сначала фильтруем, потом преобразуем
        var result = names
            .Where(name => name.Length > 3)  // Сначала фильтр
            .Select(name => name.ToUpper()); // Потом преобразование
        Console.WriteLine($"Результат: {string.Join(", ", result)}");
        // Вывод: ВАСЯ, ПЕТЯ, МАША, ИВАН
    }
}

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

Пример 3: Сортировка и работа с объектами

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    // Создаём простой класс для примера
    class Student
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public double Grade { get; set; }
        
        public override string ToString() => $"{Name} ({Age} лет, оценка: {Grade})";
    }
    
    static void Main()
    {
        // Создаём список студентов
        List<Student> students = new List<Student>
        {
            new Student { Name = "Аня", Age = 20, Grade = 4.5 },
            new Student { Name = "Вася", Age = 19, Grade = 3.8 },
            new Student { Name = "Петя", Age = 21, Grade = 4.2 },
            new Student { Name = "Маша", Age = 20, Grade = 5.0 }
        };
        
        // 1. Сортировка по возрасту (по возрастанию)
        var byAge = students.OrderBy(s => s.Age);
        Console.WriteLine("По возрасту:");
        foreach (var s in byAge)
            Console.WriteLine($"  {s}");
        
        // 2. Сортировка по оценке (по убыванию)
        var byGradeDesc = students.OrderByDescending(s => s.Grade);
        Console.WriteLine("
По оценке (от лучших к худшим):");
        foreach (var s in byGradeDesc)
            Console.WriteLine($"  {s}");
        
        // 3. Сортировка по нескольким полям: сначала по возрасту, потом по имени
        var multiSort = students
            .OrderBy(s => s.Age)
            .ThenBy(s => s.Name);
        Console.WriteLine("
По возрасту, затем по имени:");
        foreach (var s in multiSort)
            Console.WriteLine($"  {s}");
    }
}

Объяснение: OrderBy и OrderByDescending сортируют по одному полю. Если нужно сортировать по нескольким полям, используйте ThenBy и ThenByDescending. Обратите внимание: эти методы не изменяют исходный список, а возвращают новый.

Пример 4: Агрегатные функции (подсчёт статистики)

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        // Данные о продажах за неделю
        List<double> sales = new List<double> { 150.50, 230.00, 89.99, 450.00, 120.00, 300.00, 175.50 };
        
        // 1. Count — сколько всего продаж?
        int totalSales = sales.Count;
        Console.WriteLine($"Всего продаж: {totalSales}"); // 7
        
        // 2. Count с условием — сколько продаж больше 200?
        int bigSales = sales.Count(s => s > 200);
        Console.WriteLine($"Продаж > 200: {bigSales}"); // 3
        
        // 3. Sum — общая выручка
        double totalRevenue = sales.Sum();
        Console.WriteLine($"Общая выручка: {totalRevenue:C}"); // 1515.99
        
        // 4. Average — средняя продажа
        double averageSale = sales.Average();
        Console.WriteLine($"Средняя продажа: {averageSale:C}"); // 216.57
        
        // 5. Min и Max — минимальная и максимальная продажа
        double minSale = sales.Min();
        double maxSale = sales.Max();
        Console.WriteLine($"Мин: {minSale:C}, Макс: {maxSale:C}"); // 89.99, 450.00
        
        // 6. Aggregate — свой собственный подсчёт (например, произведение всех чисел)
        double product = sales.Aggregate((acc, x) => acc * x);
        Console.WriteLine($"Произведение всех продаж: {product:E}"); // Научная нотация
    }
}

Объяснение: Count, Sum, Average, Min, Max — это стандартные статистические функции. Aggregate — это универсальный инструмент: он берёт первый элемент как начальное значение, а потом применяет вашу функцию к каждому следующему элементу, накапливая результат.

Пример 5: Работа со словарём (Dictionary)

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        // Создаём словарь: ключ — имя студента, значение — его оценка
        Dictionary<string, double> grades = new Dictionary<string, double>
        {
            { "Аня", 4.5 },
            { "Вася", 3.8 },
            { "Петя", 4.2 },
            { "Маша", 5.0 }
        };
        
        // 1. ContainsKey — проверяем, есть ли студент
        bool hasVasya = grades.ContainsKey("Вася");
        Console.WriteLine($"Есть Вася? {hasVasya}"); // True
        
        // 2. TryGetValue — безопасно получаем значение
        if (grades.TryGetValue("Петя", out double peteGrade))
        {
            Console.WriteLine($"Оценка Пети: {peteGrade}"); // 4.2
        }
        
        // 3. Keys и Values — получаем все ключи или все значения
        var allNames = grades.Keys.ToList();
        var allGrades = grades.Values.ToList();
        Console.WriteLine($"Студенты: {string.Join(", ", allNames)}");
        Console.WriteLine($"Оценки: {string.Join(", ", allGrades)}");
        
        // 4. Where для словаря — фильтруем по значению
        var excellentStudents = grades.Where(kvp => kvp.Value >= 4.5);
        Console.WriteLine("Отличники:");
        foreach (var student in excellentStudents)
            Console.WriteLine($"  {student.Key}: {student.Value}");
        
        // 5. Добавление и удаление
        grades.Add("Иван", 3.5); // Добавляем нового студента
        grades.Remove("Вася");   // Удаляем Васю
        Console.WriteLine($"Всего студентов после изменений: {grades.Count}"); // 4
    }
}

Объяснение: Словарь — это коллекция пар «ключ-значение». Он идеален для быстрого поиска по ключу. Методы ContainsKey и TryGetValue работают очень быстро (O(1)), в отличие от поиска в списке (O(n)).

Пример 6: Продвинутая работа со строками

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        // Список предложений
        List<string> sentences = new List<string>
        {
            "C# — отличный язык программирования.",
            "LINQ делает работу с данными простой.",
            "Массивы и коллекции — основа любого приложения."
        };
        
        // 1. Разбиваем каждое предложение на слова
        var allWords = sentences.SelectMany(s => s.Split(' '));
        Console.WriteLine("Все слова:");
        Console.WriteLine(string.Join(", ", allWords));
        
        // 2. Группировка слов по первой букве
        var groupedByFirstLetter = allWords
            .GroupBy(word => word[0])
            .Select(group => $"Буква '{group.Key}': {group.Count()} слов");
        Console.WriteLine("
Группировка по первой букве:");
        foreach (var group in groupedByFirstLetter)
            Console.WriteLine(group);
        
        // 3. Соединяем слова в одно предложение
        string combined = string.Join(" ", allWords);
        Console.WriteLine($"
Объединённая строка: {combined}");
        
        // 4. Удаляем знаки препинания и считаем длину каждого слова
        var cleanWords = allWords
            .Select(w => w.Trim('.', ',', '!', '?')) // Убираем знаки препинания
            .Where(w => w.Length > 0);               // Убираем пустые строки
        Console.WriteLine("
Очищенные слова:");
        foreach (var word in cleanWords)
            Console.WriteLine($"  {word} (длина: {word.Length})");
    }
}

Объяснение: SelectMany — это мощный метод, который «разворачивает» вложенные коллекции. Если у вас есть коллекция коллекций, SelectMany превращает её в одну плоскую коллекцию. GroupBy группирует элементы по ключу, возвращая коллекцию групп, где каждая группа имеет ключ и список элементов.

Пример 7: Работа с HashSet и уникальностью

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        // HashSet — коллекция уникальных элементов
        HashSet<int> numbers1 = new HashSet<int> { 1, 2, 3, 4, 5 };
        HashSet<int> numbers2 = new HashSet<int> { 4, 5, 6, 7, 8 };
        
        // 1. UnionWith — объединение множеств
        HashSet<int> union = new HashSet<int>(numbers1);
        union.UnionWith(numbers2);
        Console.WriteLine($"Объединение: {string.Join(", ", union)}");
        // Вывод: 1, 2, 3, 4, 5, 6, 7, 8
        
        // 2. IntersectWith — пересечение (общие элементы)
        HashSet<int> intersect = new HashSet<int>(numbers1);
        intersect.IntersectWith(numbers2);
        Console.WriteLine($"Пересечение: {string.Join(", ", intersect)}");
        // Вывод: 4, 5
        
        // 3. ExceptWith — разность (что есть в первом, но нет во втором)
        HashSet<int> except = new HashSet<int>(numbers1);
        except.ExceptWith(numbers2);
        Console.WriteLine($"Разность: {string.Join(", ", except)}");
        // Вывод: 1, 2, 3
        
        // 4. SymmetricExceptWith — симметрическая разность (элементы, которые есть только в одном из множеств)
        HashSet<int> symmetric = new HashSet<int>(numbers1);
        symmetric.SymmetricExceptWith(numbers2);
        Console.WriteLine($"Симметрическая разность: {string.Join(", ", symmetric)}");
        // Вывод: 1, 2, 3, 6, 7, 8
        
        // 5. IsSubsetOf и IsSupersetOf — проверка на подмножество
        HashSet<int> smallSet = new HashSet<int> { 1, 2 };
        Console.WriteLine($"smallSet является подмножеством numbers1? {smallSet.IsSubsetOf(numbers1)}"); // True
        Console.WriteLine($"numbers1 является надмножеством smallSet? {numbers1.IsSupersetOf(smallSet)}"); // True
    }
}

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

Сравнение с Python: чем похоже и чем отличается

Если вы уже знакомы с Python, вам будет проще понять C#. Но есть важные отличия, которые нужно знать.

Операция C# Python Комментарий
Фильтрация list.Where(x => x > 5) filter(lambda x: x > 5, list) или [x for x in list if x > 5] В C# используется лямбда-выражение, в Python — lambda или list comprehension
Преобразование list.Select(x => x * 2) map(lambda x: x * 2, list) или [x * 2 for x in list] Аналогично фильтрации
Сортировка list.OrderBy(x => x) sorted(list) В C# нужно явно указывать, по какому полю сортировать
Проверка наличия list.Contains(5) 5 in list В Python используется оператор in, в C# — метод
Объединение строк string.Join(", ", list) ", ".join(list) Почти одинаково, но