Шаг 150.
Язык программирования C#. Начала
Наследование. Примеры использования
   
На этом шаге мы рассмотрим несколько примеров решения задач, в которых используется наследование.
   
Здесь мы рассмотрим несколько программ, в которых используются ранее рассмотренные свойства и ииндексаторы. 
   
Задание 1.Напишите программу, в которой использована цепочка наследования из трех классов. В первом классе есть открытое символьное поле. Во втором классе 
появляется открытое текстовое поле. В третьем классе появляется открытое целочисленное иоле. В каждом из классов должен быть конструктор, позволяющий создавать объект на основе 
значений полей, переданных аргументами конструктору, а также конструктор создания копии.
Раскрыть/скрыть решение и комментарии.
   
Программа, решающая данную задачу, достаточно простая. За основу мы взяли программу из 145 шага и немного ее изменили. Вот ее текст.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace pr150_1
{
    // Базовый класс: 
    class Alpha
    {
        // Открытое поле: 
        public char alpha;
        // Виртуальный метод: 
        public virtual void show()
        {
            Console.WriteLine("Класс Alpha: {0}", alpha);
        }
        // Конструктор с одним аргументом: 
        public Alpha(char a)
        {
            alpha = a;
        }
        // Конструктор создания копии:
        public Alpha(Alpha obj)
        {
            alpha = obj.alpha;
        }
    }
    // Производный класс от класса Alpha: 
    class Bravo : Alpha
    {
        // Открытое поле: 
        public string bravo;
        // Переопределение виртуального метода: 
        public override void show()
        {
            Console.WriteLine("Класс Bravo: {0} и {1}", alpha, bravo);
        }
        // Конструктор с двумя аргументами:
        public Bravo(char a, String b)
            : base(a)
        {
            bravo = b;
        }
        // Конструктор создания копии:
        public Bravo(Bravo obj)
            : base(obj)
        {
            bravo = obj.bravo;
        }
    }
    // Производный класс от класса Bravo: 
    class Charlie : Bravo
    {
        // Открытое поле: 
        public int charlie;
        // Переопределение виртуального метода: 
        public override void show()
        {
            Console.WriteLine("Класс Charlie: {0}, {1} и {2}", alpha, bravo, charlie);
        }
        // Конструктор с тремя аргументами: 
        public Charlie(char a, string b, int c)
            : base(a, b)
        {
            charlie = c;
        }
        // Конструктор создания копии:
        public Charlie(Charlie obj)
            : base(obj)
        {
            charlie = obj.charlie;
        }
    }
    // Класс с главным методом:
    class Program
    {
        static void Main()
        {
            // Главный метод:
            // Создание объекта класса Alpha:
            Alpha objA = new Alpha('A');
            Alpha objA_copy = objA;
            // Проверка значения поля: 
            objA.show();
            Console.WriteLine("Работает конструктор копирования:");
            objA_copy.show();
            Console.WriteLine();
            // Создание объекта класса Bravo:
            Bravo objB = new Bravo('A', "BCDE");
            // Объектной переменной базового класса присваивается 
            // ссылка на объект производного класса: 
            objA = objB;
            // Проверка значений полей: 
            objA.show();
            objB.show();
            Bravo objB_copy = objB;
            Console.WriteLine("Работает конструктор копирования:");
            objB_copy.show();
            Console.WriteLine();
            // Создание объекта класса Charlie:
            Charlie objC = new Charlie('F', "GHIJ", 10);
            // Объектной переменной базового класса присваивается 
            // ссылка на объект производного класса: 
            objA = objC;
            objB = objC;
            // Проверка значений полей: 
            objA.show();
            objB.show();
            objC.show();
            Charlie objC_copy = objC;
            Console.WriteLine("Работает конструктор копирования:");
            objC_copy.show();
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять 
здесь.
   
Результат работы приложения приведен на рисунке 1.

Рис.1. Результат работы приложения
   
Единственное, что здесь может вызвать затруднение, так это создание конструктора копирования в производных классах. Нужно не забывать вызывать конструктор копирования 
из базовх классов, что и сделано путем использования конструкции base в заголовке конструктора копирования. Остальные комментарии можно посмотреть на 145 шаге.
 
Задание 2. 
Напишите программу, в которой есть базовый класс с открытым целочисленным полем. В классе описан виртуальный индексатор, позволяющий считывать цифры в десятичном 
представлении числа (значение поля). На основе базового класса создается производный класс, в котором появляется еще одно открытое целочисленное поле. В производном классе 
описывается версия индексатора с двумя индексами: первый индекс определяет поле, значение которого используется, а второй индекс определяет разряд, для которого считывается цифра. 
Индексатор с одним индексом переопределяется так, что вычисления (считывание цифры в десятичном представлении числа) производятся на основе значения, равного сумме значений 
полей индексируемого объекта.
Раскрыть/скрыть решение и комментарии.
   
Приведем текст программы, решающей указанную задачу:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace pr150_2
{
    // Базовый класс: 
    class Alpha
    {
        // Целочисленное поле: 
        public int ch1;
        // Виртуальный целочисленный индексатор 
        // с целочисленным индексом: 
        public virtual int this[int k]
        {
            // Аксессор для считывания значения: 
            get
            {
                // Сохраняем значение поля:
                int t = ch1;
                int kl = 0, ch = 0;
                // Отсчитываем с конца требуемую цифру:
                while (kl < k) {
                    kl++;
                    ch = t % 10;
                    t = t / 10;
                }
                // Возвращаем результат:
                return ch;
            }
        }
    }
    // Производный класс: 
    class Bravo : Alpha {
        // Целочисленное поле: 
        public int ch2;
        // Определение индексатора с двумя 
        // целочисленными индексами:
        public int this[int ch, int k]
        {
            // Аксессор для считывания значения: 
            get
            {
                // Определяем, из какого поля
                // берем значение:
                int t;
                if (ch == 1) t = base.ch1;
                else t = ch2;
                int kl = 0, c = 0;
                // Отсчитываем с конца требуемую цифру:
                while (kl < k) {
                    kl++;
                    c = t % 10;
                    t = t / 10;
                }
                // Возвращаем результат:
                return c;
            }
        }
        // Переопределение индексатора с целочисленным индексом: 
        public override int this[int k]
        {
            // Аксессор для считывания значения: 
            get
            {
                // Складываем значения полей:
                int t = base.ch1 + ch2;
                int kl = 0, ch = 0;
                // Отсчитываем с конца требуемую цифру:
                while (kl < k) {
                    kl++;
                    ch = t % 10;
                    t = t / 10;
                }
                // Возвращаем результат:
                return ch;
            }
        }
    }
    // Класс с главным методом: 
    class Program
    {
        // Главный метод:
        static void Main()
        {
            // Создаем объект класса Alpha:
            Alpha A = new Alpha();
            // Определяем значение поля:
            A.ch1 = 87615;
            // Выводим значения поля и 
            // индексатора:
            Console.WriteLine("А.ch1 = " + A.ch1);
            Console.WriteLine("А[2] = " + A[2]);
            Console.WriteLine();
            // Создаем объект класса Bravo:
            Bravo B = new Bravo();
            // Определяем значения полей:
            B.ch2 = 23456;
            B.ch1 = 12345;
            // Выводим значения полей и 
            // индексаторов:
            Console.WriteLine("B.ch1 = " + B.ch1);
            Console.WriteLine("B.ch2 = " + B.ch2);
            Console.WriteLine("B[1, 4] = " + B[1, 4]);
            Console.WriteLine("B[2, 3] = " + B[2, 3]);
            Console.WriteLine();
            int s = B.ch1 + B.ch2;
            Console.WriteLine("B.ch1 + B.ch2 = " + s);
            Console.WriteLine("B[3] = " + B[3]);
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять 
здесь.
   
Результат работы приложения изображен на рисунке 2.

Рис.2. Результат работы приложения
   
Основная сложность приложения - реализация get-акссесора в числовом индексаторе. Дадим краткие комментарии к его реализации.
   
В производном классе в индексаторе с двумя целочисленными индексами, первый параметр ch определяет, из какого класса будем брать значение целочисленного поля. 
Для того, чтобы показать, что берется поле из базового класса, явно указана конструкция base. В остальном же алгоритм аналогичен использованному в базовом классе.
   
В производном классе при переодределении индексатора с одним целочисленным индексом сначала складываем значения полей из базового и производного классов, а потом 
возвращаем значение требуемого разряда.
   
В главном методе создаем объекты указанных классов, придаем значения полям и демонстрируем использование созданных в классах методов.
 
Задание 3. Напишите программу, в которой есть базовый класс с защищенным целочисленным массивом, индексатором (с целочисленным индексом), позволяющим 
считывать и присваивать значения элементам массива, а также свойство, возвращающее результатом размер массива. На основе базового класса создается производный класс, у которого 
появляется защищенный символьный массив. Опишите в производном классе версию индексатора с символьным индексом, который возвращает значение элемента символьного массива и 
позволяет присвоить значение элементу символьного массива. Для свойства из базового класса необходимо выполнить замещение так, чтобы результатом возвращался целочисленный 
массив из двух элементов: первый элемент определяет размер целочисленного массива объекта, а второй элемент определяет размер символьного массива объекта
Раскрыть/скрыть решение и комментарии.
   
Программа, решающая эту задачу, приведена ниже:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace pr150_3
{
    // Базовый класс: 
    class Alpha
    {
        // Защищенный целочисленный массив: 
        protected int[] num;
        // Конструктор с одним аргументом: 
        public Alpha(int[] t)
        {
            // Присваивание значения полю: 
            num = t;
        }
        // Целочисленное свойство: 
        public int number
        {
            // Аксессор для считывания значения: 
            get
            {
                // Возвращает размер массива:
                return num.Length;
            }
        }
        // Целочисленный индексатор с целочисленным индексом: 
        public int this[int k]
        {
            // Аксессор для считывания значения: 
            get
            {
                return num[k];
            }
            // Аксессор для присваивания значения: 
            set
            {
                num[k] = value;
            }
        }
        // Переопределение метода ToString(): 
        public override string ToString()
        {
            // Текстовая переменная: 
            string txt = "|";
            // Формируется значение текстовой переменной: 
            for (int k = 0; k < num.Length; k++)
            {
                txt += num[k] + "|";
            }
            // Результат метода: 
            return txt;
        }
    }
    // Производный класс: 
    class Bravo : Alpha
    {
        // Защищенный символьный массив: 
        protected char[] symb;
        // Конструктор с двумя аргументами: 
        public Bravo(int[] t, string txt): base(t){
            // Присваивание значения полю: 
            // Создание массива на основе текстового аргумента: 
            symb = txt.ToCharArray();
        }
        // Символьный индексатор с символьным индексом: 
         public char this[char s] {
             // Аксессор для считывания значения: 
             get {
                 return symb[s - 'a'];
             }
             // Аксессор для присваивания значения: 
             set {
                 symb[s - 'a'] = (char)value;
             }
         }
        // Замещение свойства: 
        new public int[] number
        {
            // Аксессор для считывания значения: 
            get
            {
                int
            // Аксессор для считывания значения: 
            get
            {
                 int[] rez = {num.Length, symb.Length};
                // Возвращает размер массива:
                return rez;
            }
        }
    }
    // Класс с главным методом:
    class Program
    {
        // Главный метод:
        static void Main()
        {
            // Создание и инициализация массива:
            int[] numsA = { 1, 3, 5, 7, 6, 5, 4 };
            // Создание объекта:
            Alpha objA = new Alpha(numsA);
            // Проверка содержимого массива объекта:
            Console.WriteLine("Объект Alpha: ");
            Console.WriteLine(objA);
            Console.WriteLine("objA[2] = {0}", objA[2]);
            objA[2] = 200;
            Console.WriteLine("Измененное значение objA[2] = {0}", objA[2]);
            Console.WriteLine();
            int[] numsB = { 10, 30, 50 };
            // Создание объекта:
            Bravo objB = new Bravo(numsB,"KLMNOP");
            // Проверка содержимого объекта:
            Console.WriteLine("Объект Bravo: ");
            Console.WriteLine("objB[2] = {0}", objB[2]);
            objB[2] = 400;
            Console.WriteLine("Измененное значение objB[2] = {0}", objB[2]);
            // Индексирование объекта:
            objB['a'] = 'X';
            objB['d'] = 'Y';
            Console.WriteLine("objB[\'a\'] = " + objB['a']);
            Console.WriteLine("objB[\'d\'] = " + objB['d']);
            Console.WriteLine();
            int[] masB = objB.number;
            Console.WriteLine("objB.number = {0}, {1}", masB[0], masB[1]);
            Console.WriteLine();
            // Проверка содержимого массива объекта:
            Console.WriteLine(objB);
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять 
здесь.
   
Результат работы приложения изображен на рисунке 3.

Рис.3. Результат работы приложения
   
При реализации этой программы иы кое-что взяли из примера приложения, приведенного на 148 шаге. 
Попробуйте разобраться с ней самостоятельно. 
 
   
На этом первая часть нашего изложения закончена. Мы познакомились с основными, наиболее важными темами. Это основа, ядро языка С#. На данном этапе вы  уже вполне 
подготовлены для написания сложных программ. Тем не менее точку в изучении языка С# мы не ставим. Впереди еще много полезного и интересного. Планы у нас большие, и 
их воплощению посвящена следующий раздел.
   
Во второй части обсуждаются несколько фундаментальных концепций, которые в полной мере раскрывают красоту и мощь языка С#. Там мы познакомимся с интерфейсами и 
абстрактными классами. Узнаем, что такое делегаты, ссылки на методы, лямбда-выражения и анонимные методы, познакомимся с событиями. Познакомимся с перечислениями и выясним, 
чем структуры отличаются от классов. Мы научимся с помощью указателей выполнять операции с памятью. Узнаем, как в языке C# реализуется перехват и обработка ошибок. 
Для нас откроются секреты создания многопоточных приложений. Мы научимся использовать обобщенные типы и сможем создавать приложения с графическим интерфейсом. Еще будут 
операции с файлами, знакомство с коллекциями и многое другое. Так что работа предстоит большая и интересная. 
   
На следующем шаге мы перечислим темы, изучением которых планируем заняться в дальнейшем.
Предыдущий шаг  Содержание
 
Содержание  Следующий шаг
 
Следующий шаг