Всем привет. Начиная с этой статьи начинаю цикл статей про нововведения в C#
8
версии.
Сейчас мы рассмотрим работу с индексами (Indices
). Забегая вперед, скажу, что теперь мы, C#
разработчики, можем работать с индексами как в Python
.
Пристегнитесь. Начинаем?
Пусть в нашей программе есть массив целых чисел numbers
:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
}
Перед нами стоит задача получить последний элемент массива.
Мы с вами знаем, что доступ к элементам массива осуществляется по индексу. Следует помнить, что индексация начинается с нуля – первый элемент массива имеет индекс 0
, а последний – n - 1
, где n
– размер массива:
Если мы заранее знаем количество элементов в массиве, то можно чуть упростить себе жизнь ? Для нашего примера, мы знаем, что в массиве 6
элементов, следовательно, чтобы получить последний элемент, нужно обратиться к элементу с индексом 5
, то есть написать следующим образом:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var lastNumber = numbers[5];
}
Это конечно же временное решение. Не всегда мы заранее знаем количество элементов в массиве. Например, есть некоторый метод Test
, внутри которого для получения результата нужно получить последний элемент переданного массива:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
Test(numbers);
}
static void Test(int[] numbers)
{
// Так уже не напишешь.
// Мы внутри метода не знаем какое количество элементов в массиве
var lastNumber = numbers[5];
}
В метод могли передать массив из любого количества элементов, следовательно, нам нужен универсальный механизм/алгоритм для получения последнего элемента для любого массива.
Как мы до этого уже говорили, можно рассчитать последний элемент с помощью длины массива, так как последний элемент имеет индекс – n - 1
, где n
– размер/длина массива. Для того, чтобы получить размерность массива, нужно воспользоваться функцией Length
:
static void Test(int[] numbers)
{
// Получаем длину массива
var length = numbers.Length;
// Получаем последний элемент</span>
var lastNumber = numbers[length - 1];
}
Какие есть в этом коде минусы:
Можно забыть отнять единицу и получить ошибку во время выполнения работы. Поверьте, начинающие программисты очень часто забывают про это.
Код становится менее читаемым.
Решение
В C#
8
версии добавили дополнительную функциональность для работы с индексами. Я напомню, что была индексация слева направо, начинающаяся с 0
. Теперь добавили новую индексацию справа налево (начинается с конца массива), начинающаяся с 1
. Для лучшего понимания рассмотрим таблицу:
Индексация слева направо | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
Массив | 5 | 1 | 4 | 2 | 3 | 7 |
Индексация справа налево | 6 | 5 | 4 | 3 | 2 | 1 |
Жирным выделен сам массив. Над ним привычная нам индексация, а под ним новая индексация.
Для использования новой индексации необходимо перед значением индекса поставить символ ^
. Пишется данный символ на английской раскладке с помощью комбинации клавиш shift + 6
:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
Console.WriteLine(numbers[^1]); // 7
Console.WriteLine(numbers[^5]); // 1
Console.WriteLine(numbers[^6]); // 5
}
Таблица получения каждого элемента двумя способами:
Выражение | Выражение | Результат | Пояснение |
---|---|---|---|
numbers[0] | numbers[^6] | 5 | Первый элемент |
numbers[1] | numbers[^5] | 1 | Второй элемент |
numbers[2] | numbers[^4] | 4 | Третий элемент |
numbers[3] | numbers[^3] | 2 | Четвертый элемент |
numbers[4] | numbers[^2] | 3 | Пятый элемент |
numbers[5] | numbers[^1] | 7 | Шестой элемент |
Запомните:
Теперь появился универсальный, удобный и логически понятный способ получения последнего элемента массива. В массиве любого размера для получения последнего элемента нужно обратиться к индексу ^1
, например, numbers[^1]
.
Теперь зная про новый способ индексации перепишем нашу функцию Test
:
static void Test(int[] numbers)
{
// Получаем последний элемент</span>
var lastNumber = numbers[^1];
}
ВНИМАНИЕ!
Отсчет индексации справа налево начинается с 1
, а не с 0
. Если указать значение меньше 1 или больше индекса первого элемента, то возникнет исключение - ArgumentOutOfRangeException
:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
Console.WriteLine(numbers[^0]); // ArgumentOutOfRangeException
Console.WriteLine(numbers[^7]); // ArgumentOutOfRangeException
}
Что там под капотом?
На самом деле любой индекс с появлением C#
8
версии можно хранить в новом типе данных Index
. Он находится в пространстве имен (namespace
) System
, следовательно, никакой дополнительный using
при его использовании не нужно писать.
У Index
существует два конструктора:
Index()
– создает индекс, указывающий на первый элемент массива (с индексом0
).Index(int value, bool fromEnd = false)
– позволяет задать значение индекса (value
) и определить ведется ли отчет от начала (fromEnd = false
) или от конца массива (fromEnd = true
). Соответственно важно помнить, что для индексов с отсчетом от конца массива счет начинается с1
(а не с0
как в обычном случае). Заметьте, что второй параметр по умолчанию имеет значениеfalse
.
Рассмотрим на примерах:
static void Main()
{
var numbers = new int[] {5, 1, 4, 2, 3, 7};
var indexFirst = new Index(0); // индекс первого элемента
Console.WriteLine(numbers[indexFirst]); // 5
indexFirst = new Index(); // индекс первого элемента
Console.WriteLine(numbers[indexFirst]); // 5
var indexLast = new Index(1, fromEnd: true); // индекс последнего элемента
Console.WriteLine(numbers[indexLast]); // 7
var index1 = new Index(3); // индекс 3 элемента
Console.WriteLine(numbers[index1]); // 2
index1 = new Index(3, fromEnd: false); // индекс 3 элемента
Console.WriteLine(numbers[index1]); // 2
var index2 = new Index(5, fromEnd: true); // индекс 5 элемента с конца
Console.WriteLine(numbers[index2]); // 1
}
Заметьте, что объект типа Index
передается в качестве индекса в квадратные скобки ([]
).
Проведем соответствие между двумя разными записями:
Укороченная версия | Версия c |
---|---|
|
|
|
|
В Index
реализовано неявное преобразование целого числа (int
) к Index
. Вот как это работает:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
Index index1 = 2;
Console.WriteLine(numbers[index1]); // 4
Index index2 = ^2;
Console.WriteLine(numbers[index2]); // 3
}
У Index
переопределен метод Equals
:
static void Main()
{
var index1 = new Index(3);
var index2 = new Index(3);
Console.WriteLine(index1.Equals(index2)); // true
}
А можно вообще вот так:
static void Main()
{
var index1 = new Index(3);
Console.WriteLine(index1.Equals(3)); // true
}
Здесь сначала происходит неявное преобразование 3
к Index
, а потом вызов Equals
.
У Index
переопределен также метод ToString
:
static void Main()
{
var index1 = new Index(3);
var index2 = new Index(3, fromEnd: true);
Console.WriteLine(index1.ToString()); // 3
Console.WriteLine(index2.ToString()); // ^3
}
Заметьте, что для индексации с конца выводится ^
перед индексом.
Так как теперь мы можем индекс хранить в переменной типа Index
, то можно чуть переписать нашу функцию Test
:
static void Test(int[] numbers)
{
// Сохраняем последний индекс
Index lastIndex = ^1;
// Получаем последний элемент
var lastNumber = numbers[lastIndex];
// Какие то действия, которые меняют элементы массива
lastNumber = numbers[lastIndex];
// Какие то действия, которые меняют элементы массива
lastNumber = numbers[lastIndex];
}
Мы сохранили в переменной lastIndex
индекс последнего элемента и использовали во всех местах.
Также теперь мы можем в методы передавать индекс:
static void Test(int[] numbers, Index index)
{
// логика
}
Выводы
Структура
Index
позволяет создать экземпляр, к которому можно обращаться многократно;Код становится более короткий и читаемый;
Удобно обращаться к элементам массива в обратном порядке.
PS. Написано с любовью вместе со своими учениками. Они у меня лучшие ?