Шаг 127.
Язык программирования C#. Начала
Свойства и индексаторы. Знакомство с индексаторами

    На этом шаге мы рассмотрим определение и использование индексаторов.

    Теперь пришло время познакомиться с индексаторами. Индексатор является специальным членом класса, наличие которого в классе позволяет индексировать объекты класса. Когда мы говорим об индексировании объекта, то подразумеваем, что после имени объекта в квадратных скобках указывается индекс (или индексы) - конечно, при условии, что такое выражение имеет смысл. Таким образом, описав в классе индексатор, мы получаем возможность обращаться с объектом класса так, как если бы этот объект был массивом. Это очень важная и перспективная возможность.

    Прежде чем приступить к изучению вопроса о том, как описывается индексатор, остановимся на том, что происходит при индексировании объекта (в случае, когда в соответствующем классе описан индексатор). Итак, представим, что имеется объект, который индексируется после имени объекта в квадратных скобках указан индекс (для простоты будем полагать, что индекс один). Есть две ситуации, когда такая инструкция может быть использована. В одном случае мы хотим узнать (прочитать) значение данного выражения. В другом случае такому выражению может присваиваться значение. И та и другая ситуация разрешается способом, подобным к тому, как это делается для свойства, а именно - с каждый индексатором связано два специальных метода-аксессора. При считывании значения выражения вызывается get-аксессор. При присваивании значения выражению вызывается set-аксессор. Единственное принципиальное отличие от свойства состоит в том, что в случае с индексатором в операциях считывания значения и присваивания значения участвует еще и индекс.

    Описывается индексатор следующим образом. Кроме спецификатора уровня доступа (обычно это ключевое слово public), указывается ключевое слово, определяющее тип значения, возвращаемого при индексировании объекта. Затем указывается ключевое слово this. После ключевого слова this в квадратных скобках указывается тип индекса и его формальное обозначение. После этого в блоке из фигурных скобок описываются аксессоры. В общем случае их два (хотя может быть и индексатор только с одним аксессором): это get-аксессор, который вызывается при считывании значения выражения с проиндексированным объектом, и set-аксессор, который вызывается при присваивании значения выражению с проиндексированным объектом. Аксессоры описываются в принципе так же, как и для свойства, но кроме ключевого слова value, определяющего присваиваемое значение в set-аксессоре, в обоих аксессорах еще используется и переменная, обозначающая индекс. Ниже представлен шаблон для описания индексатора:

тип_индексатора this[тип_индекса индекс] {
  // Метод вызывается при считывании значения выражения
  // с проиндексированным объектом:
  get {
    // Код get-аксессора
  } 
  // Метод вызывается при присваивании значения выражению
  //с проиндексированным объектом:
  set {
    // Код set-аксессора
  }
}

    Часто индексатор описывается с целочисленным индексом (тип индекса указывается как int). Но это не обязательно - тип индекса может быть и другим. Более того, индексатор можно перегружать - в классе может быть описано несколько индексаторов, которые отличаются количеством и/или типом индексов. Вместе с тем индексатор не может быть статическим, а также индексатор нельзя использовать с идентификаторами ref и out.


Напомним, что ключевое слово this, используемое в описании индексатора, может иметь и иное назначение. Так, в программном коде класса, в описании методов (в том числе аксессоров, конструкторов и деструкторов) ключевое слово this может использоваться для обозначения объекта, из которого вызывается метод.

    В примере ниже представлена программа, в которой есть класс, а в этом классе описан индексатор.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace pr127_1
{
    // Класс с индексатором: 
    class MyClass {
        // Закрытое поле, являющееся ссылкой на массив: 
        private int[] nums;
        // Конструктор с целочисленным аргументом: 
        public MyClass(int n){
            // Создание массива: 
            nums = new int[n];
            // Заполнение массива:
            for (int k = 0; k < nums.Length; k++){
                nums[k] = 0;
            }
        }
        // Переопределение метода ToString(): 
        public override string ToString(){
            // Формирование текстовой строки: 
            string txt = "{" + nums[0]; 
            for(int k = 1; k < nums.Length; k++) { 
                txt += "," + nums[k];
            }
            txt += "}";
            // Результат метода:
            return txt;
    }

    // Целочисленное свойство: 
    public int length {
        // Метод вызывается при считывании значения свойства: 
        get {
            // Значение свойства:
            return nums.Length;
        }
    }
    
    // Целочисленный индексатор с целочисленным индексом: 
    public int this[int k]{
        // Метод вызывается при считывании значения
        // объекта с индексом:
        get {
            // Значение выражения: 
            return nums[k];
        }
        // Метод вызывается при присваивании значения 
        // объекту с индексом:
        set {
            // Присваивание значения элементу массива: 
            nums[k] = value;
        }
    }
    }

    // Класс с главным методом:
    class Program
    {
        static void Main()
        {
            // Создание объекта:
            MyClass obj = new MyClass(5);
            // Отображение содержимого массива из объекта: 
            Console.WriteLine(obj);
            // Присваивание значений элементам массива из объекта 
            // с использованием индексирования объекта: 
            for(int k = 0; k < obj.length; k++){
                // Используется индексирование объекта: 
                obj[k] = 2 * k + 1;
            }
            // Отображение содержимого массива из объекта:
            Console.WriteLine(obj);
            // Поэлементное отображение массива из объекта 
            // с использованием индексирования объекта: 
            for (int k = 0; k < obj.length; k++){
                // Используется индексирование объекта:
                Console.Write(" " + obj[k]);
            }
            Console.WriteLine();
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

    При выполнении программы получаем такой результат.


Рис.1. Результат выполнения программы

    Описывая класс MyClass, мы предусмотрели в нем закрытое поле nums, представляющее собой ссылку на целочисленный массив. В классе переопределен метод ToString(), так что результатом метода возвращается текстовая строка со значениями элементов массива. Конструктору класса передается один целочисленным аргумент, определяющим размер массива, на который ссылается поле nums. Массив создается при выполнении кода конструктора. Также все элементы массива получают нулевое значение.


Строго говоря, при создании массива его элементы автоматически получают нулевые значения. Тем не менее мы в конструкторе добавили блок кода (оператор цикла), которым элементам массива присваиваются начальные значения.

    В классе есть целочисленное свойство length, у которого имеется только get-аксессор. Значением свойства length является размер массива, на который ссылается поле nums (определяется выражением nums.Length). Понятно, что присвоить значение свойству length нельзя.

    Описание целочисленного индексатора с целочисленным индексом начинается с ключевого слова public. Идентификатор int означает, что результатом выражения, в котором есть объект с индексом, является целое число. Также целое число можно присвоить значением такому выражению.


Возможность прочитать значение или присвоить значение выражению на основе проиндексированного объекта определяется наличием или отсутствием соответствующего аксессора.

    После обязательного ключевого слова this в квадратных скобках указана инструкция int k. Она определяет тип и имя индекса. То есть в программном коде аксессоров для этого индексатора под k следует подразумевать индекс, указанный в квадратных скобках после имени объекта. И этот индекс является целочисленным (тип int). В теле get-аксессора всего одна команда

  return nums[k];          . 
Таким образом, если после имени объекта в квадратных скобках указать индекс, то значением такого выражения является значение элемента массива nums с таким же индексом.

    В set-аксессоре выполняется команда

  nums[k] = value;     . 
Ключевое слово value отождествляется с присваиваемым значением. По факту оно присваивается элементу массива nums с индексом k. Получается, что когда мы присваиваем значение объекту с индексом, то в действительности такое значение получит элемент с таким же индексом в массиве объекта.

    В главном методе программы мы проверяем работу индексатора. Для этого мы командой

  MyClass obj = new MyClass(5);
создаем объект obj с массивом из пяти элементов. Вначале этот массив заполнен нулями; проверяем это командой
  Console.WriteLine(obj);      . 
Затем выполняется цикл, в котором перебираются элементы массива в объекте obj. Размер массива вычисляется выражением obj.length. За каждую итерацию цикла (при фиксированном индексе k) командой
  obj[k] = 2 * k + 1;
соответствующему элементу массива присваивается значение. После того как мы проверяем содержимое массива в объекте, с помощью еще одной циклической конструкции мы поэлементно выводим значения элементов массива в консоль. При этом используется индексирование объекта obj (инструкция вида obj[k]).

    На следующем шаге мы начнем рассматривать использование индексаторов.




Предыдущий шаг Содержание Следующий шаг