Pull to refresh

Индексы (Indices) в C# 8

Reading time5 min
Views17K

Всем привет. Начиная с этой статьи начинаю цикл статей про нововведения в 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];
}

Какие есть в  этом коде минусы:  

  1. Можно забыть отнять единицу и получить ошибку во время выполнения работы. Поверьте, начинающие программисты очень часто забывают про это.  

  2. Код становится менее читаемым. 

Решение

В 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. Он находится в пространстве имен (namespaceSystem, следовательно, никакой дополнительный 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

numbers[2]

numbers[new Index(2)] 

или 

numbers[new Index(2, fromEnd: false)]

numbers[^2]

numbers[new Index(2, fromEnd: true)]

В 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)
{
	// логика
}

Выводы

  1. Структура Index позволяет создать экземпляр, к которому можно обращаться многократно;

  2. Код становится более короткий и читаемый;

  3. Удобно обращаться к элементам массива в обратном порядке.


PS. Написано с любовью вместе со своими учениками. Они у меня лучшие ?

Only registered users can participate in poll. Log in, please.
Понравилась данная фича?
21.35% Нет41
41.67% Да80
36.98% Даже не знаю71
192 users voted. 9 users abstained.
Tags:
Hubs:
Total votes 7: ↑7 and ↓0+7
Comments32

Articles