Вы знаете, что доступ к индивидуальным элементам, содержащимся в стандартных массивах, осуществляется через операцию индекса ([ ]). В языке C# имеется возможность проектировать специальные классы и структуры, которые могут быть индексированы подобно стандартному массиву, посредством определения индексатора (альтернатива массиву). Если вы можете обойтись массивами, забудьте об индексаторах. Важно, что основное назначение индексатора — предоставить пользователю функциональные возможности, аналогичные работе с массивом. Это языковое средство полезно при создании специальных типов коллекций, когда их размер заранее неизвестен.
Если все же вам еще интересно, зачем они нужны, то начнем с определений. Индексаторы могут быть одно- или многомерными.
Одномерные индексаторы
Общая форма одномерного индексатора:
тип_элемента this[int индекс] { get { // Возврат значения, которое определяет индекс. } set { // Установка значения, которое определяет индекс. } }
где тип_элемента обозначает конкретный тип элемента индексатора. Следовательно, у каждого элемента, доступного с помощью индексатора, должен быть определенный тип_элемента. Этот тип соответствует типу элемента массива. Параметр индекс получает конкретный индекс элемента, к которому осуществляется доступ. Формально этот параметр совсем не обязательно должен иметь тип int, но чаще всего используется целочисленный тип данного параметра.
В теле индексатора определены два аксессора (средства доступа к данным, см. также статью о свойствах): get и set. Аксессор подобен методу, за исключением того, что в нем не объявляется тип возвращаемого значения или параметры. Аксессоры вызываются автоматически при использовании индексатора, и оба получают индекс в качестве параметра. Так, если индексатор указывается в левой части оператора присваивания, то вызывается аксессор set и устанавливается элемент, на который указывает параметр индекс. В противном случае вызывается аксессор get и возвращается значение, соответствующее параметру индекс. Кроме того, аксессор set получает неявный параметр value, содержащий значение, присваиваемое по указанному индексу.
Давайте рассмотрим пример:
using System; namespace индексаторы { class MA { object[] ar; public int Leng; public MA(int Size) { ar = new object[Size]; Leng = Size; } // Создаем простейший индексатор public object this[int index] { set { ar[index] = value; } get { return ar[index]; } } } class Program { static void Main() { MA a1 = new MA(Size: 5); // Инициализируем каждый экземпляра класса a1 a1[0] = 5; a1[1] = 3.14; a1[2] = 'Y'; a1[3] = "Russia"; a1[4] = true; for (int i = 0; i < 5; i++) Console.Write("{0}\t", a1[i]); Console.WriteLine("\nДлина - {0}", a1.Leng); Console.ReadLine(); } } }
Результат:
В текущем классе MA определен индексатор а1, позволяющий вызывающему коду идентифицировать элементы (объекты) с применением числовых значений индекса (как в массиве). В статье Массивы найдите пример с объявлением массива объектов, сравните коды и найдите отличия.
На применение индексаторов накладываются два существенных ограничения. Во-первых, значение, выдаваемое индексатором, нельзя передавать методу в качестве параметра ref или out, поскольку в индексаторе не определено место в памяти для его хранения. И во-вторых, индексатор должен быть членом своего класса и поэтому не может быть объявлен как static.
Многомерные индексаторы
Пример индексатора, принимающего несколько параметров:
using System; namespace индексаторы { class MA2 { int[,] ar; // Размерность двухмерного массива public int r, c; public int Leng; // конструктор public MA2(int rows, int cols) { this.r = rows; this.c = cols; ar = new int[this.r, this.c]; Leng = r * c; } // Индексатор public int this[int ind1, int ind2] { get { return ar[ind1, ind2]; } set { ar[ind1, ind2] = value; } } } class Program { static void Main(string[] args) { Random ran = new Random(); MA2 a2 = new MA2(4, 5); for (int i = 0; i < a2.r; i++) { for (int j = 0; j < a2.c; j++) { a2[i, j] = ran.Next(50,90); Console.Write(a2[i, j] + "\t"); } Console.WriteLine(); } Console.ReadLine(); } } }
Результат:
После инициализации индексатора далее с ним можем работать как с двухмерным массивом целых чисел. Небольшой плюс индексатора: размерности (число строк и столбцов) являются членами класса. Замените 2 строку в методе Main( ) на:
int[,] a2 = new int[4, 5];
т.е. объявим массив a2. Тогда в задании верхних границ индексов будет ошибки типа:
«System.Array» не содержит определения для «r» и не был найден метод расширения «r», принимающий тип «System.Array» в качестве первого аргумента.
Впрочем, если объявите класс МА3:
class MA3 { public int[,] ar; // Размерность двухмерного массива public int r, c; public int Leng; // конструктор public MA3(int rows, int cols) { this.r = rows; this.c = cols; ar = new int[this.r, this.c]; Leng = r * c; } }
и внесете изменения в вызов элементов массива (выделено красным):
class Program { static void Main(string[] args) { Random ran = new Random(); MA3 a2 = new MA3(4, 5); for (int i = 0; i < a2.r; i++) { for (int j = 0; j < a2.c; j++) { a2.ar[i, j] = ran.Next(50,90); Console.Write(a2.ar[i, j] + "\t"); } Console.WriteLine(); } Console.ReadLine(); } }
то вы получите практически тоже, что и с индексатором. Плюс только в том, что вы обращаетесь к элементу индексатора a2[i, j], а к элементу массива через поле ar: a2.ar[i, j].
Примечания.
- Современные языки программирования предлагают вам различные варианты реализации тех или иных классов, выбор наиболее удобного – это ваш выбор.
- Объявление индексатора очень похоже на объявление свойств с аксессорами set и get. Поэтому понятно, почему свойства и индексаторы относятся к член-функциям класса.
NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.