Индексы и диапазоны


В C# 8.0 была добавлена новая функциональность - индексы и диапазоны, которые упрощают получение из массивов подмассивов. Для этого в C# есть два типа: System.Range и System.Index. Оба типа являются структурами. Тип Range представляет некоторый диапазон значений в некоторой последовательность, а тип Index - индекс в последовательности.

Индексы
Индекс фактически представляет числовое значение, и при определении индекса мы можем указать это значение:

Index myIndex = 2;
В данном случае индекс представляет третий элемент последовательности (индексация начинается с 0).

С помощью специального оператора ^ можно задать индекс относительно конца последовательности.

Index myIndex = ^2;
Теперь индекс представляет второй элемент с конца последовательности, то есть предпоследний элемент.

Используем индексы для получения элементов массива:

Index myIndex1 = 2; // третий элемент
Index myIndex2 = ^2; // предпоследний элемент

string[] people = { "Tom", "Bob", "Sam", "Kate", "Alice" };
string selected1 = people[myIndex1]; // Sam
string selected2 = people[myIndex2]; // Kate
Console.WriteLine(selected1);
Console.WriteLine(selected2);
Фактически для данной задачи индексы не нужны, и мы можем воспользоваться стандартными возможностями массивов:

string[] people = { "Tom", "Bob", "Sam", "Kate", "Alice" };
string selected1 = people[2]; // Sam
string selected2 = people[people.Length - 2]; // Kate
Console.WriteLine(selected1);
Console.WriteLine(selected2);
То есть в подобных ситуациях плюсом индексов является большая удобочитаемость. Так, people[^2] более читабельно, чем people[people.Length - 2].

Диапазон
Диапазон представляет часть последовательности, которая ограничена двумя индексами. Начальный индекс включается в диапазон, а конечный индекс НЕ входит в диапазон. Для определения диапазона применяется оператор ..:

Range myRange1 = 1..4; // по 1-го индекса включая по 4-й индекс не включая
В данном случае диапазон myRange1 влючает элементы с 1 индекса по 4-й индекс (не включая). При этом элемент по 4-му индексу не включается в диапазон. При этом границы диапазона задаются не просто числами, а именно объектами Index. То есть следующие определения диапазонов будут равноценны:

Index start = 1;
Index end = 4;
Range myRange1 = start..end;

Range myRange2 = 1..4;
Практическое применение диапазонов - получим со второго по четвертый элементы массива:

string[] people = { "Tom", "Bob", "Sam", "Kate", "Alice" };
string[] peopleRange = people[1..4]; // получаем 2, 3 и 4-й элементы из массива
foreach(var person in peopleRange)
{
Console.WriteLine(person);
}
Результатом операции people[1..4] является подмассив элементов с 1 по 3 индексы (включая). Консольный вывод:

Bob
Sam
Kate
Мы можем задать для диапазона только конечный индекс. В этом случае начальным индексом по умолчанию будет 0.

string[] people = { "Tom", "Bob", "Sam", "Kate", "Alice" };
string[] peopleRange = people[..4]; // Tom, Bob, Sam, Kate
Либо, наоборот, задать только начальный индекс, тогда конечным индексом будет последний индекс последовательности:

string[] people = { "Tom", "Bob", "Sam", "Kate", "Alice" };
string[] peopleRange = people[1..]; // Bob, Sam, Kate, Alice
Используя индексы относительно конца последовательности, можно получать диапазон относительно конца последовательности:

string[] people = { "Tom", "Bob", "Sam", "Kate", "Alice" };
string[] peopleRange1 = people[^2..]; // два последних - Kate, Alice
string[] peopleRange2 = people[..^1]; // начиная с предпоследнего - Tom, Bob, Sam, Kate
string[] peopleRange3 = people[^3..^1]; // два начиная с предпоследнего - Sam, Kate
Кроме массивов индексы и диапазоны также применяются к объектам Span и ReadOnlySpan:

string[] people = { "Tom", "Bob", "Sam", "Kate", "Alice" };
Span<string> peopleSpan = people;
Span<string> selectedPeopleSpan = peopleSpan[1..4];
foreach (var person in selectedPeopleSpan)
{
Console.WriteLine(person);
}