JavaRush /Курсы /C# SELF /Новый метод LINQ: Index

Новый метод LINQ: Index

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

1. Эволюция боли

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

// Всё привычно, классика C#:
for (int i = 0; i < list.Count; i++)
{
    Console.WriteLine($"{i}: {list[i]}");
}

Но если вы начинали писать на LINQ, то вдруг… вы теряли индекс! Все эти красивые .Where, .Select, .OrderBy внезапно не дают вам номер элемента — только сам элемент. Конечно же, хотелось бы делать так:

list.SelectWithIndex((item, index) => ...);

Но стандартного метода вроде SelectWithIndex никогда не было. Хотя с помощью перегрузки Select и можно получить индекс, но только… когда хочется использовать индекс без трансформации или без проекции, приходилось городить велосипед с дополнительными .Select, а это делало красивый LINQ-код менее понятным.

Как выживали до .NET 9

В былые времена у программистов C# были свои хакерские трюки:

var result = list.Select((item, index) => new { item, index });

А ещё — комбинации с другими LINQ-методами, но всегда как-то не очень "встроено", и не совсем то, чего хотелось бы для красоты.

2. Новый метод LINQ: Index — что это и зачем?

Официальное описание

В .NET 9 команда разработчиков учла стенания миллионов программистов (ну ладно, десятков тысяч в Twitter, что тоже немало) — и добавила в LINQ новый метод-расширение — Index. На официальной странице документации .NET 9 можно найти следующее:

Index() добавляет к каждому элементу последовательности его порядковый номер, начиная с нуля, и возвращает пары (значение, индекс), без необходимости явно создавать анонимные объекты или писать .Select((item, idx) => new { item, idx }).

Это мощно — теперь Index просто возвращает вам для каждого элемента пару: сам элемент и его индекс.

Сигнатура метода

public static IEnumerable<(T Element, int Index)> Index<T>(this IEnumerable<T> source);

Перевод на человеческий: Для каждого элемента коллекции ты получаешь специальный "кортеж" — Element и его Index. Всё. Больше тебе не нужно городить анонимные типы.

3. Примеры использования метода Index

Очень простой пример

Давайте возьмём список любимых фруктов.

var fruits = new List<string> { "Яблоко", "Банан", "Апельсин", "Киви" };

foreach (var (fruit, idx) in fruits.Index())
{
    Console.WriteLine($"{idx}: {fruit}");
}

Вывод:

0: Яблоко
1: Банан
2: Апельсин
3: Киви

Вот так просто! Теперь вы можете элегантно получить пару "индекс-значение" и использовать их внутри любого LINQ-запроса.

Интеграция с остальными LINQ-методами

Index — это полноценный участник LINQ-семьи! Его можно спокойно вставлять в "цепочки".

Пример 1: Отфильтровать нечётные элементы по индексу

var numbers = Enumerable.Range(10, 10); // 10, 11, ... 19
var oddIndexes = numbers.Index()
                        .Where(pair => pair.Index % 2 == 1)
                        .Select(pair => pair.Element);

Console.WriteLine(string.Join(", ", oddIndexes));

Вывод:

11, 13, 15, 17, 19

Пример 2: Модифицировать элементы с чётными индексами

var users = new List<string> { "Анна", "Игорь", "Катя", "Денис" };
var modified = users.Index()
                    .Select(pair => pair.Index % 2 == 0 ? pair.Element.ToUpper() : pair.Element.ToLower());

foreach (var name in modified)
    Console.WriteLine(name);

Вывод:

АННА
игорь
КАТЯ
денис

Пример 3: Объединение с другими коллекциями по индексу (zip-like)

var ids = new[] { 101, 102, 103 };
var names = new[] { "Alice", "Bob", "Charlie" };

var merged = names.Index()
                  .Join(ids.Index(), 
                        namePair => namePair.Index,
                        idPair => idPair.Index,
                        (namePair, idPair) => (idPair.Element, namePair.Element));

foreach (var (id, name) in merged)
    Console.WriteLine($"{id}: {name}");

Вывод:

101: Alice
102: Bob
103: Charlie

4. Index в реальном приложении

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

Пример: Выводим список студентов с номерами

Допустим, у нас уже есть класс Student из предыдущих примеров:

public class Student
{
    public string Name { get; set; }
    public int Grade { get; set; }
}

Создадим небольшой список студентов:

var students = new List<Student>
{
    new Student { Name = "Даша", Grade = 5 },
    new Student { Name = "Петя", Grade = 3 },
    new Student { Name = "Вова", Grade = 4 },
    new Student { Name = "Оля", Grade = 5 }
};

Теперь, используя Index, красиво выводим всех с номерами:

foreach (var (student, idx) in students.Index())
{
    Console.WriteLine($"{idx + 1}. {student.Name} — Оценка: {student.Grade}");
}
Здесь idx + 1 — чтобы индексация шла привычно с единицы, а не с нуля.

Результат:

1. Даша — Оценка: 5
2. Петя — Оценка: 3
3. Вова — Оценка: 4
4. Оля — Оценка: 5

Практическая польза: Теперь, если пользователь захочет выбрать студента по номеру — всё готово! Код стал проще, никаких "ручных" счетчиков, максимум читаемости — минимум багов.

5. Сравнение: чем Index круче старых подходов?

До .NET 9: старый стиль

Раньше для получения вместе элемента и его индекса приходилось использовать перегрузку .Select((item, index) => ...) и конструировать анонимные типы:

var withIndexes = students.Select((student, index) => new { student, index });

Чтобы получить нужные поля, постоянно приходилось писать .student, .index — да ещё и тип анонимный, никакого красивого имёнованного кортежа.

С .NET 9: стиль XXI века

Теперь — не нужно заботиться о полях, анонимных типах. Всё прозрачно:

foreach (var (student, idx) in students.Index())
{
    // работает из коробки, интуитивно, просто, красиво
}

Код стал чище. Меньше кода, меньше ошибок. Всё идеально подходит для больших цепочек LINQ, где не хочется думать о дополнительных перегрузках.

6. Тонкости и особенности использования

Область применения

Index работает с любым объектом, реализующим интерфейс IEnumerable<T>. То есть — со всеми обычными коллекциями, массивами, результатами других LINQ-запросов.

Какой тип возвращает Index?

Он возвращает перечисление кортежей, где первый элемент — сам объект (обычно называют Element), второй — Index (тип int). Благодаря современному синтаксису кортежей в C# мы можем прямо в цикле писать foreach (var (element, index) in ...) и получать оба значения в нужные переменные сразу.

Можно ли использовать с Query Syntax?

Нет, Index — это метод-расширение, query syntax (SQL-подобный LINQ) его не поддерживает напрямую. То есть вот так не выйдет:

// Не работает!
var query = from s in students.Index() select ...;

Если хочется совместить, просто заключите нужный метод в скобки и работайте с результатом как с обычной коллекцией:

var query = from pair in students.Index()
            where pair.Index > 1
            select pair.Element;

Индексация: всегда с нуля

Index всегда начинает отсчёт с нуля, как и большинство подобных штук в программировании C#. Если вам нужно начинать с единицы — просто добавьте 1 в нужном месте.

8. Ошибки и особенности — на что обратить внимание

Многие студенты на первых порах путаются между Index и перегрузкой .Select((item, index) => ...). Ошибки чаще всего такие:

— Пытаются использовать Index в query syntax: "Почему не работает?" — а он работает только как метод-расширение.

— Ожидают, что индекс начнётся с 1, а он, разумеется, начинается с 0.

— Думают, что Index изменяет исходную коллекцию — но, как и все LINQ-методы, он возвращает новую последовательность, не модифицируя исходную (immutable-подход).

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

2
Задача
C# SELF, 32 уровень, 3 лекция
Недоступна
Нумерация строк в списке
Нумерация строк в списке
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Паша Гриц Уровень 1
20 августа 2025
Задание: Для нумерации строк необходимо использовать метод LINQ `Index`. использование colors.Index() - ошибка (при этом все работает правильно) правильное решение, сгенерированое системой var numberedColors = colors.Select((color, index) => $"{index}: {color}"); мне кажется, или тут противоречие?
Ra Уровень 35 Student
10 декабря 2025
ДА