Всем привет. Начиная с этой статьи начинаю цикл статей про нововведения в 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. Написано с любовью вместе со своими учениками. Они у меня лучшие ?
ссылка на оригинал статьи https://habr.com/ru/articles/649043/
Добавить комментарий