Шаг 148.
Язык программирования C#. Начала
Наследование. Переопределение и перегрузка методов

    На этом шаге мы рассмотрим особенности выполнения указанных операций.

    Ранее при изучении вопросов, связанных с наследованием, мы в основном имели дело с полями и методами. Но наследоваться могут также индексаторы и свойства. Более того, они даже могут быть виртуальными (то есть их можно переопределять как виртуальные методы). Ничего удивительного здесь нет, особенно если вспомнить, что механизм реализации свойств и индексаторов базируется на использовании методов-аксессоров. Небольшой пример, в котором имеет место наследование индексаторов и свойств, представлен в примере ниже.

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

namespace pr148_1
{
    // Базовый класс: 
    class Alpha {
        // Защищенное целочисленное поле: 
        protected int alpha;
        // Закрытый массив: 
        private char[] symbs;
        // Конструктор с двумя аргументами: 
        public Alpha(int a, string txt) {
            // Присваивание значения полю: 
            alpha = a;
            // Создание массива на основе текстового аргумента: 
            symbs = txt.ToCharArray(); 
        }
        // Виртуальное свойство: 
        public virtual int number {
            // Аксессор для считывания значения: 
            get {
                return alpha;
            }
            // Аксессор для присваивания значения: 
            set {
                alpha = value;
            }
        }
        // Открытое свойство: 
        public int length
        {
            // Аксессор для считывания значения: 
            get
            {
                return symbs.Length;
            }
        }
         // Символьный индексатор с целочисленным индексом: 
         public char this[int k] {
             // Аксессор для считывания значения: 
             get {
                 return symbs[k];
             }
             // Аксессор для присваивания значения: 
             set {
                 symbs[k] = value;
             }
         }
        // Виртуальный целочисленный индексатор 
        // с символьным индексом: 
        public virtual int this[char s] {
            // Аксессор для считывания значения: 
            get {
                // Используется индексирование объекта: 
                return this[s - 'a'];
            }
            // Аксессор для присваивания значения: 
            set {
                // Используется индексирование объекта: 
                this[s - 'a'] = (char)value;
            }
        }
        // Переопределение метода ToString(): 
        public override string ToString() {
            // Текстовая переменная: 
            string txt = "|";
            // Формируется значение текстовой переменной: 
            for(int k=0; k < this.length; k++) {
                txt += this[k] + "|";
            }
            // Результат метода: 
            return txt;
        }
    }

    // Производный класс: 
    class Bravo: Alpha {
        // Защищенное целочисленное поле: 
        protected int bravo;
        // Конструктор с тремя аргументами: 
        public Bravo(int a, int b, string txt): base(a, txt) { 
            // Значение целочисленного поля: 
            bravo = b;
        }
        // Переопределение свойства: 
        public override int number {
            // Аксессор для считывания значения: 
            get {
                return alpha + bravo;
            }
        }
        // Переопределение индексатора с символьным индексом: 
        public override int this[char s] {
            // Аксессор для считывания значения: 
            get {
                if (s == 'a') return alpha; 
                else return bravo;
            }
            // Аксессор для присваивания значения: 
            set {
                if (s == 'a') 
                    alpha = value;
                else bravo = value;
            }
        }
    }

    // Класс с главным методом: 
    class Program
    {
        // Главный метод:
        static void Main()
        {
            // Целочисленная переменная: 
            int k;
            // Символьная переменная: 
            char s;
            // Создание объекта базового класса:
            Alpha A = new Alpha(100, "ABCD");
            Console.WriteLine("Объект A:");
            // Содержимое символьного массива объекта:
            Console.WriteLine(A);
            // Значение свойства number объекта:
            Console.WriteLine("А.number = " + A.number);
            // Присваивание значения свойству number:
            A.number = 150;
            // Значение свойства number объекта:
            Console.WriteLine("A.number = " + A.number);
            // Индексирование объекта: 
            for(k=0, s='a'; k < A.length; k++, s++){
                Console.WriteLine("Символ \'{0}\' с кодом {1}", A[k], A[s]);
            }
            A[0] = 'E';
            A['b'] = 'A' + 10;
            // Содержимое символьного массива объекта:
            Console.WriteLine(A);
            // Создание объекта производного класса:
            Bravo B = new Bravo(200, 300, "EFGHI");
            Console.WriteLine("Объект В:");
            // Содержимое массива объекта:
            Console.WriteLine(B);
            // Значение свойства number объекта:
            Console.WriteLine("В.number = " + B.number);
            // Присваивание значения свойству number объекта:
            B.number = 400;
            // Значение свойства number объекта:
            Console.WriteLine("В.number = " + B.number);
            // Индексирование объекта:
            B['a'] = 10;
            B['d'] = 20;
            Console.WriteLine("В[\'a\'] = " + B['a']);
            Console.WriteLine("В[\'b\'] = " + B['b']);
            // Проверка значения свойства number объекта:
            Console.WriteLine("В.number = " + B.number);
            // Индексирование объекта: 
            for(k=0; k < B.length; k++){
                Console.Write(B[k] + " ");
                B[k] = (char)('a' + k);
            }
            Console.WriteLine();
            // Проверка содержимого массива объекта:
            Console.WriteLine(B);
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

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


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

    В этой программе мы описываем базовый класс Alpha и производный от него класс Bravo. В классе Alpha есть защищенное целочисленное поле alpha, а также закрытый символьный массив symbs (поле, являющееся ссылкой на массив). Конструктор класса имеет два аргумента. Первый целочисленный аргумент определяет значение целочисленного поля alpha. Второй текстовый аргумент служит основанием для создания символьного массива. Чтобы на основе текста получить массив (состоящий из букв, формирующих текст), мы использовали библиотечный метод ToCharArray().

    В классе Alpha описано виртуальное (в описании использовано ключевое слово virtual) целочисленное свойство number (это свойство переопределяется в производном классе). Значением свойства возвращается значение целочисленного поля alpha, а при присваивании значения свойству значение присваивается этому полю. Еще одно свойство length доступно только для чтения и результатом возвращает размер символьного массива.

    В классе Alpha описано два индексатора. Есть там символьный индексатор с целочисленным индексом. Результатом выражения с проиндексированным объектом возвращается значение элемента символьного массива symbs, при присваивании значения проиндексированному объекту оно присваивается элементу массива symbs с соответствующим индексом. Еще есть целочисленный индексатор с символьным индексом. Этот индексатор виртуальный (описан с ключевым словом virtual) и переопределяется в производном классе. В базовом классе он описан так, что для заданного символьного индекса s результатом get-аксессора возвращается значение выражения this[s-'а']. Здесь ключевое слово this обозначает индексируемый объект. Значением выражения s-'a' является целое число, равное разности кодов символов: от кода символьного значения переменной s вычитается код символа 'а'. В итоге получается ключевое слово this, проиндексированное целочисленным индексом. Такое выражение обрабатывается индексатором с целочисленным индексом. Результатом является символ из символьного массива. Но поскольку все это описано в get-аксессоре, который должен возвращать результатом целое число, то символ автоматически будет преобразован в числовое значение (код символа). Таким образом, при индексировании объекта символьным индексом результатом будет возвращаться код символа из символьного массива. Индекс элемента в массиве определяется символьным индексом объекта: символ 'а' соответствует индексу 0, символ 'b' соответствует индексу 1 и так далее.

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

  this[s - 'a'] = (char)value;
которой присваиваемое числовое значение преобразуется в символьное значение, и это символьное значение присваивается элементу символьного массива. Принцип индексации такой же, как и в get-аксессоре.

    В классе Alpha переопределен метод ToString(), который результатом возвращает текстовую строку, содержащую символы из символьного массива. При этом в описании метода для определения размера массива мы использовали выражение this.length на основе свойства length с явным указанием ссылки на объект this, а для получения значения элемента массива использовалась инструкция this[k], означающая индексирование объекта.

    В главном методе программы объект А класса Alpha создается командой

  Alpha A = new Alpha(100, "ABCD");     .
Содержимое символьного массива, формируемого на основе текстовой строки "ABCD", проверяем командой
  Console.WriteLine(A);     . 
В последнем случае для преобразования объекта А к текстовому формату будет задействован метод ToSring(). При считывании значения свойства number (инструкция A.number) результатом является значение поля alpha. При присваивании значения свойству number (команда
  A.number = 150;
) значение присваивается полю alpha.

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

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

  A[0] = 'E';
элемент с индексом 0 в символьном массиве symbs получает значение 'Е', а при выполнении команды
  A['b'] = 'A' + 10;
элемент с индексом 1 в символьном массиве symbs получает значением символ, код которого вычисляется выражением 'А'+10 (число 75, являющееся кодом символа 'К').

    Производный класс Bravo создается наследованием класса Alpha. Класс Bravo получает такое "наследство":

- все это, так сказать, "в неизменном виде". Также в классе Bravo наследуются и переопределяются: Кроме этого, в классе Bravo описано защищенное целочисленное поле bravo. У конструктора класса три аргумента (два целочисленных и один текстовый).

    При переопределении свойства number мы его еще раз описываем в производном классе, причем используем ключевое слово override. Более того, в производном классе данное свойство описано только с get-аксессором. Если в классе Alpha значение свойства определялось значением поля alpha, то в классе Bravo значение свойства number вычисляется как сумма значений полей alpha и bravo. Поскольку set-аксессор не описан, то будет использоваться set-аксессор свойства number из класса Alpha. Таким образом, если присвоить значение свойству number объекта класса Bravo, то значение будет присвоено полю alpha. Если прочитать значение свойства number объекта класса Bravo, то результатом получим сумму значений полей alpha и bravo.

    Индексатор с символьным индексом в классе Bravo определяется совершенно иначе по сравнению с классом Alpha. Теперь если проиндексировать объект символом 'а', то получаем доступ к полю alpha объекта. Если указать любой другой символьный индекс, получим доступ к полю bravo объекта.

    Объект В класса Bravo создается в главном методе программы командой

  Bravo B = new Bravo(200, 300, "EFGHI");    . 
Для проверки содержимого символьного массива объекта используем команду
  Console.WriteLine(B);   , 
при выполнении которой для преобразования объекта В к текстовому формату вызывается метод ToString(), унаследованный из класса Alpha.


Символьный массив symbs из класса Alpha в классе Bravo не наследуется в том смысле, что в теле класса Bravo мы не можем напрямую обратиться к массиву по имени. Тем не менее массив существует и доступ к нему осуществляется индексированием объектов, а размер массива можно узнать с помощью свойства length.

    При считывании значения свойства number объекта В (инструкция В.number) значением возвращается сумма полей alpha и bravo объекта В. При выполнении команды

  B.number = 400;
полю alpha присваивается значение 400.

    Командой

  B['a'] = 10;
полю alpha присваивается значение 10, а при выполнении команды
  B['d'] = 20;
значение 20 присваивается полю bravo. Соответственно, значением выражения В['а'] возвращается значение поля alpha, а значением выражения В['b'] возвращается значение поля bravo.


Если при индексировании объекта В мы указали символьный индекс 'а', то выполняется обращение к полю alpha объекта В. Если мы указали любой другой символьный индекс (например, 'd' или 'b'), то обращение выполняется к полю bravo объекта В.

    В цикле индексная переменная k пробегает значения индексов элементов символьного массива в объекте В. Размер массива определяем выражением В.length. За каждую итерацию цикла отображается значение элемента массива (инструкция В[k]), после чего командой

  B[k] = (char)('a' + k);
элементу присваивается новое значение. По завершении цикла командой
  Console.WriteLine(B);
проверяется содержимое массива объекта В.

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




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