Шаг 133.
Язык программирования C#. Начала
Свойства и индексаторы. Двумерные индексаторы (окончание)

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

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


Термин "элемент" в данном примере используется в двух смыслах. Во-первых, мы условно полагаем, что объект класса содержит некоторые "виртуальные" элементы, которые имеют значение (целочисленные) и доступ к которым осуществляется по двум ключам (символ и текст). Во-вторых, есть три массива: символьный, текстовый и целочисленный. Массивы одинакового размера. Между элементами массивов существует строгое соответствие: если зафиксировать некоторый целочисленный индекс, то элементы в символьном и текстовом массивах с этим индексом определяют значения ключей нашего "виртуального" элемента, а элемент в целочисленном массиве с таким же индексом определяет значение нашего "виртуального" элемента.

    С помощью индексатора мы реализуем две операции:

При считывании значения в символьном и текстовом массивах объекта выполняется поиск элементов, значения которых совпадают со значениями индексов. Причем совпадение должно быть "двойное": значение элемента в символьном массиве должно совпадать со значением символьного индекса, а значение элемента на такой же позиции в текстовом массиве должно совпадать со значением текстового индекса. Если такое совпадение есть, то значением выражения возвращается значение соответствующего элемента из целочисленного массива. Если совпадение не найдено, то в каждый из трех массивов (символьный, текстовый и целочисленный) добавляется новый элемент. Значение добавляемого элемента такое: для символьного и текстового массивов это значения индексов, а для целочисленного массива значение добавляемого элемента равно 0.

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


Операцию добавления нового элемента в массив следует понимать в том смысле, что создается новый массив, размер которого на единицу больше, чем размер исходного массива. В новый массив копируются значения элементов из исходного массива, а последний элемент получает значение того элемента, который "добавляется". Ссылка на новый массив записывается в переменную массива. Создается иллюзия, что в исходном массиве появился новый элемент.

    Рассмотрим представленный ниже программный код.

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

namespace pr133_1
{
    // Класс с двумерным индексатором: 
    class MyClass {
        // Закрытое поле, являющееся ссылкой на 
        // целочисленный массив: 
        private int[] vals;
        // Закрытое поле, являющееся ссылкой на 
        // символьный массив: 
        private char[] ckey;
        // Закрытое поле, являющееся ссылкой на 
        // текстовый массив: 
        private string[] skey;
        // Закрытый метод для добавления новых элементов 
        // в массивы:
        private void add(char a, string b, int n){
            // Локальная переменная для записи размера 
            // новых массивов: 
            int size;
            // Переменная для символьного массива: 
            char[] s;
            // Переменная для текстового массива: 
            string[] t;
            // Переменная для целочисленного массива: 
            int[] v;
            // Определение размера новых массивов: 
            if (vals == null) size = 1; // Если массиза нет 
            else size = vals.Length + 1; // Если массив существует 
            // Создание символьного массиза: 
            s = new char[size];
            // Значение последнего элемента созданного массива: 
            s[s.Length - 1] = a;
            // Создание текстового массива: 
            t = new string[size];
            // Значение последнего элемента созданного массива: 
            t[t.Length - 1] = b;
            // Создание целочисленного массива:
            v = new int[size];
            // Значение последнего элемента созданного массива: 
            v[v.Length - 1] = n;
            // Заполнение созданных массивов: 
            for (int k = 0; k < size - 1; k++){ 
                s[k] = ckey[k]; 
                t[k] = skey[k]; 
                v[k] = vals[k];
            }
            // Новые значения полей:
            ckey = s;
            skey = t;
            vals = v;
        }

        // Переопределение метода ToString(): 
        public override string ToString(){
            // Текстовая переменная:
            string txt = "Содержимое объекта:\n";
            // Если массив существует: 
            if (vals != null) {
                // Перебор элементов массивов: 
                for (int k = 0; k < ckey.Length; k++){
                    // Добавление текста к текущему значению: 
                    txt += ckey[k] + ": " + skey[k] + ": " + vals[k] + "\n";
                }
            } else {// Если массива нет
                // Добавление текста к текущему значению: 
                txt += "Пустой объект\n";
            }
            // Результат метода: 
            return txt;
        }

        // Целочисленный индексатор с двумя индексами 
        // символьного и текстового типа: 
        public int this[char a, string b]{
            // Метод вызывается при считывании значения 
            // выражения с проиндексированным объектом: 
            get {
                // Если массив существует: 
                if (vals != null) {
                    // Перебор элементов массива: 
                    for (int k = 0; k < ckey.Length; k++){
                        // Если элемент найден: 
                        if (a == ckey[k] && b==skey[k]){
                            // Значение выражения: 
                            return vals[k];
                        }
                    }
                }
                // Код выполняется, если массив не существует или 
                // если элемент не найден: 
                int res = 0; // Значение для нового элемента 
                add(a, b, res); // Добавление нового элемента 
                return res; // Значение выражения
            }
            // Метод вызывается при присваивании значения 
            // выражению с проиндексированным объектом: 
            set {
                // Если массив существует: 
                if (vals != null) {
                    // Перебираются элементы массива: 
                    for (int k = 0; k < ckey.Length; k++){
                        // Если элемент найден: 
                        if (a == ckey[k] && b == skey[k]) {
                            // Присваивание значения элементу: 
                            vals[k] = value;
                            // Завершение метода: 
                            return;
                        }
                    }
                }
                // Если массива нет или если элемент не найден: 
                add(a, b, value); // Добавление нового элемента
            }
        }
    }

    // Класс с главным методом: 
    class Program
    {
        // Главный метод: 
        static void Main()
        {
            // Создание объекта:
            MyClass obj = new MyClass();
            // Проверка содержимого объекта:
            Console.WriteLine(obj);
            // Проверка значения элемента:
            Console.WriteLine("Значение элемента: " + obj['А', "Первый"] + "\n"); 
            // Проверка содержимого объекта:
            Console.WriteLine(obj);
            // Присваивание значения элементу: 
            obj['В', "Второй"] = 200;
            // Присваивание значения элементу: 
            obj['С', "Третий"] = 300;
            // Проверка содержимого объекта:
            Console.WriteLine(obj);
            // Проверка значения элемента:
            Console.WriteLine("Значение элемента: " + obj['В', "Первый"] + "\n"); 
            // Проверка значения элемента:
            Console.WriteLine("Значение элемента: " + obj['В', "Второй"] + "\n"); 
            // Проверка значения элемента:
            Console.WriteLine("Значение элемента: " + obj['А', "Третий"] + "\n"); 
            // Проверка содержимого объекта:
            Console.WriteLine(obj);
            // Присваивание значения элементу: 
            obj['А', "Первый"] = 100;
            // Проверка содержимого объекта:
            Console.WriteLine(obj);
            // Проверка значения элемента:
            Console.WriteLine("Значение элемента: " + obj['А', "Первый"] + "\n");
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

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


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

    В классе MyClass, как отмечалось, есть три закрытых массива:

В классе описан закрытый метод add() с тремя аргументами. При вызове метода add() в каждый из трех массивов добавляется новый элемент. Значения элементов передаются аргументами методу. При вызове метода проверяется условие vals==null, истинное в случае, если поле vals не ссылается на массив. Если так, то целочисленная переменная size получает значение 1. Если условие ложно, то целочисленная переменная size получает значение vals.Length+1 (на единицу больше размера массива, на который ссылается поле vals). Затем создаются три массива (символьный s, текстовый t и целочисленный v), размер которых определяется значением переменной size. Последние элементы массивов получают значения, определяемые аргументами метода (команды
            s[s.Length - 1] = a;
            t[t.Length - 1] = b;
            v[v.Length - 1] = n;
). Прочие элементы массивов заполняются поэлементным копированием, для чего использована конструкция цикла (команды
                s[k] = ckey[k]; 
                t[k] = skey[k]; 
                v[k] = vals[k];
в теле цикла). Наконец, командами
            ckey = s;
            skey = t;
            vals = v;
значениями полям объекта присваиваются ссылки на новые массивы.

    Метод ToString() переопределен таким образом, что если поля объекта на массивы не ссылаются, то возвращается текстовая строка с информацией о том, что объект пустой. Если массивы существуют, то возвращается текстовая строка со значениями элементов трех массивов объекта (в одной строке значения элементов с одним и тем же индексом).

    В get-аксессоре индексатора проверяется условие vals!=null. Истинность условия означает, что целочисленный массив существует (а значит, и два других массива тоже). В этом случае синхронно перебираются все элементы символьного и текстового массивов. Для каждого индекса к проверяется условие

             if (a == ckey[k] && b==skey[k]){
(первый индекс а совпадает с элементом символьного массива, а второй индекс b совпадает с элементом текстового массива). Если совпадение найдено, значением свойства возвращается vals[k] (элемент целочисленного массива с тем же индексом).

    В случае, если массивы не существуют или если совпадение не найдено, вызывается метод add(), которым в массивы добавляется по новому элементу (или создаются массивы, если их не было), и значение (нулевое) элемента, добавленного в целочисленный массив, возвращается результатом выражения.

    В set-аксессоре выполняются аналогичные действия, но последствия немного другие. Если при просмотре содержимого массивов совпадение найдено (истинно условие

                 if (a == ckey[k] && b == skey[k]) {
), то командой
  vals[k] = value;
элементу целочисленного массива присваивается значение. После этого инструкцией return завершается выполнение set-аксессора.

    Если совпадение не найдено, то с помощью метода add() в массивы добавляется по одному новому элементу.

    В главном методе создается объект obj класса MyClass. Проверка показывает (команда

  Console.WriteLine(obj);
), что объект пустой (его поля не ссылаются на массивы). Поэтому при попытке узнать значение выражения obj['А', "Первый"] получаем 0, а в массивы объекта obj добавляется по новому элементу (понятно, что предварительно массивы создаются).

    При выполнении команд

            // Присваивание значения элементу: 
            obj['В', "Второй"] = 200;
            // Присваивание значения элементу: 
            obj['С', "Третий"] = 300;
в массивы добавляется еще но два элемента.

    Затем последовательно считываются значения выражений obj['В', "Первый"], obj['В', "Второй"] и obj['А', "Третий"]. Для первого и третьего из этих выражений в объекте obj совпадений индексов не обнаруживается, а для второго выражения совпадение есть. Поэтому для второго выражения возвращается значение 200 из целочисленного массива, а в соответствии с первым и третьим выражениями в массивы объекта добавляются новые элементы (в том числе нулевые значения в целочисленный массив).


Напомним, что при индексировании объекта символьный и текстовый массивы просматриваются на предмет "двойного" совпадения: индексы должны одновременно совпасть с соответствующими элементами символьного и текстового массивов.

    Командой

            // Присваивание значения элементу: 
            obj['А', "Первый"] = 100;
присваивается новое значение уже существующему элементу целочисленного массива. Проверка это подтверждает.

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




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