Всем привет. Начиная с этой статьи начинаю цикл статей про нововведения в C#
8
версии.
Сейчас мы рассмотрим работу с индексами (Indexes
). Забегая вперед, скажу, что теперь мы, 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. Написано с любовью вместе со своими учениками. Они у меня лучшие ????
Комментарии (30)
diogen4212
01.02.2022 16:32+7Отсчет индексации справа налево начинается с 1, а не с 0
Сколько лабораторок и собесов на джуниора провалится…Deosis
02.02.2022 07:24В статье стоит упомянуть, что индексы ввели одновременно с диапазонами, а там ^0 может использоваться и имеет довольно логичное значение.
В c++ аналогом будет итератор, возвращаемый методом end.
bm13kk
02.02.2022 11:12Или написать сразу про все фичи связанные индексами массивов. Потому что текущая статья вроде и имеет обьем, но информации маловато.
z0ic
01.02.2022 16:36А индексацию булевыми значениями array[array > 0] можно делать ?
DarthLexus
01.02.2022 19:51а это уже Range (https://docs.microsoft.com/ru-ru/dotnet/api/system.range?view=net-6.0)
int[] afterZero = array[1..^0];
ну или LINQ:
IEnumerable<int> tail = array.Where((x, i) => i > 0);
Myxach
01.02.2022 17:51Можно забыть отнять единицу и получить ошибку во время выполнения работы. Поверьте, начинающие программисты очень часто забывают про это.
Как можно забыть, если ты по идее, должен array.len-1 сохранить в отдельном переменной, если array.len не меняется
alexander222
01.02.2022 21:00+1То есть от начала мы считаем с 0, а от конца с 1. Читаемость кода теми, кто не использует эту фичу постоянно очень усложняется
enabokov
03.02.2022 12:02Я тоже разочарован этим фактом. Была какая-то причина так делать?
alexander222
03.02.2022 12:19Логика в этом есть- беря первый элемент от начала мы "пропускаем" 0 элементов, и читаем один. Беря первый от конца, мы должны сначала "пропустить" один, а потом его считать. Но для человека такая логика не сразу интуитивна
bm13kk
03.02.2022 12:19Если не использовать спец символов (как в питоне, с которым идет сравнение в этой статье) - отрицательные индексы начинаются с -1, потому что 0 - уже занят в положительных индексах.
NeoCode
01.02.2022 23:05+1Мне кажется, лучше всего сделано в D - там используется контекстный символ $, означающий размер индексируемого массива. В результате получается
int arr[] = {1,2,3,4,5}; int x = arr[0]; // 1 int y = arr[$-1]; // 5
т.е. да, мы видим что последний элемент имеет индекс "$-1" (т.е. понятно откуда в C#8 растут ноги индексации с единицы). Запись на 1 символ больше чем в C#8, зато никаких специальных типов, можно по прежнему использовать обычные целые числа.
А вот идея индексации с конца отрицательными числами, как делается в некоторых языках - не самая лучшая. Индекс может оказаться отрицательным случайно, по ошибке.
Naf2000
01.02.2022 23:26Но и за контекст не вытащить. А в шарпе это System.Index может быть параметром функции, например.
sophist
02.02.2022 10:45+3Следует помнить, что индексация начинается с нуля…
Можно забыть отнять единицу и получить ошибку во время выполнения работы. Поверьте, начинающие программисты очень часто забывают про это.
Теперь появился универсальный, удобный и логически понятный способ получения последнего элемента массива.
…важно помнить, что для индексов с отсчетом от конца массива счет начинается с 1 (а не с 0, как в обычном случае).
{
// логика
}Простите, не удержался :)
camelCaps
03.02.2022 13:04// Получаем длину массива var length = numbers.Length; // Получаем последний элемент var lastNumber = numbers[length - 1];
Какие есть в этом коде минусы:
1. Можно забыть отнять единицу и получить ошибку во время выполнения работы.
2. Код становится менее читаемым
Не согласен с обоими пунктами. Первое: с таким же успехом можно забыть и все остальное - даже то, как создать новый проект в VS. Второе: читаемость отличная; этот код можно дословно перевести на русский язык и его поймет половина НЕпрограммистов.
А вот что забыл сам автор, так это проверку
length
на0
.JosefDzeranov Автор
04.02.2022 00:18Все таки наше общество программистов ещё не доросли до общества западных программистов.
Нам бы лишь бы найти за что упрекнуть. Откуда это все идёт не понимаю
camelCaps
04.02.2022 03:27Не думайте, что западные программисты в своих сообществах только и делают, что обмазывают друг друга восхвалениями. Дискуссии на фоне несогласий у них случаются не менее пылкие, знаете ли. Безусловно, я мог бы формулировать свои мысли более деликатно (в стиле "да, но" или как-то подобно тому), но, к своему стыду, не посчитал, что отказ от этого принесет кому-либо душевных травм. Вполне возможно, представители "сообщества западных программистов" более склонны не задеть чувства окружающих, но, скорее того, отличия между "ими" и "нами" в другом: воспринимать критику как акт агрессии - это да, это наше, родное.
inklesspen
03.02.2022 13:04Напрягает лишь, что есть возможность хранить индекс объектом, и что возможность в [] указывать new Index() позволяет и поощрает создавать отдельный объект для обращения к элементу по индексу, а создавать лишние объекты - не есть хорошо. Но если данная фича также имеет поддержку compile-time развертки a[^N] в a[a.lenght - N], то вопросов меньше
DadeMurphyZC
03.02.2022 13:37+1Лично мне нравится, что появилась такая функция (индексы). Пригодится.
bm13kk
Я ожидал что-то про срезы
[start:end:step]
JosefDzeranov Автор
Скоро выйдет статья про срезы