На этом шаге мы рассмотрим еще один пример использования абстрактных классов.
Еще один пример, который мы рассмотрим далее, дает представление о том, как описывается и используется абстрактный класс, в котором есть абстрактные свойства и индексаторы.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace pr155_1 { // Абстрактный класс: abstract class Base { // Абстрактное текстовое свойство: public abstract string text { get; set; } // Абстрактный индексатор с целочисленным индексом: public abstract char this[int k] { get; } // Абстрактное целочисленное свойство: public abstract int length { get; } } // Производный класс на основе абстрактного: class Alpha: Base { // Закрытое поле, являющееся ссылкой на массив: private char[] symbs; // Конструктор: public Alpha(string t): base() { // Текстовому свойству присваивается значение: text = t; } // Переопределение текстового свойства: public override string text { get { // Результатом является текстовая строка: return new string(symbs); } set { // Создание символьного массива и присваивание // значения полю: symbs = value.ToCharArray(); } } // Переопределение целочисленного свойства: public override int length { get { // Размер массива: return symbs.Length; } } // Переопределение индексатора: public override char this[int k] { get { // Значение элемента символьного массива: return symbs[k]; } } } // Производный класс на основе абстрактного: class Bravo: Base { // Закрытое текстовое поле: private string txt; // Конструктор: public Bravo(string t): base() { // Текстовому свойству присваивается значение: text = t; } // Переопределение текстового свойства: public override string text { get { // Значение поля: return txt; } set { // Присваивание значения полю: txt = value; } } // Переопределение целочисленного свойства: public override int length { get { // Количество символов в тексте: return txt.Length; } } // Переопределение индексатора: public override char this[int k] { get { // Символ в тексте: return txt[k]; } } } // Класс с главным методом: class Program { // Главный метод: static void Main() { // Ссылка на объект производного класса записывается // в объектную переменную базового класса: Base obj = new Alpha("Alpha"); // Отображение значения текстового свойства: Console.WriteLine(obj.text); // Новое значение текстового свойства: obj.text = "Base"; // Индексирование объекта: for(int k = 0;k < obj.length; k++) { Console.Write("|" + obj[k]); } Console.WriteLine("|"); // Ссылка на объект производного класса записывается // в объектную переменную базового класса: obj = new Bravo("Bravo"); // Индексирование объекта: for(int k = 0;k < obj.length; k++) { Console.Write("|" + obj[k]); } Console.WriteLine("|"); // Новое значение текстового свойства: obj.text = "Base"; // Отображение значения текстового свойства: Console.WriteLine(obj.text); // Задержка: Console.ReadLine(); } } }
Ниже показано, как выглядит результат выполнения программы:
Рис.1. Результат выполнения программы
В программе описан абстрактный класс Base, в котором объявлены два абстрактных свойства (текстовое text и целочисленное length) и индексатор с целочисленным индексом. Все эти члены класса описаны с ключевым словом abstract. В теле абстрактного текстового свойства text указаны ключевые слова get и set (после каждого ключевого слова ставится точка с запятой). Это означает, что при переопределении (по факту при описании) свойства должен быть описан и get-аксессор, и set-аксессор. В теле свойства length и в теле индексатора указано только ключевое слово get. Поэтому при переопределении свойства и индексатора в производном классе описывается только get-аксессор.
На основе класса Base путем наследования создается класс Alpha и класс Bravo. Классы практически идентичны, но имеются отличия на "техническом" уровне. В классе Alpha используется закрытое поле symbs, являющееся ссылкой на символьный массив. В классе Bravo описано закрытое текстовое свойство txt. У каждого из классов есть конструктор с текстовым аргументом.
В каждом из конструкторов переданное аргументом текстовое значение присваивается свойству text. Но в классе Alpha процедура присваивания значения свойству text описана так, что присваиваемое текстовое значение с помощью библиотечного метода ToCharArray() преобразуется в массив и ссылка на этот массив записывается в поле symbs. В классе Bravo текстовое значение при присваивании свойству text в действительности записывается в поле txt. Значение поля txt возвращается в виде значения свойства text для объекта класса Bravo.
Для объекта класса Alpha в качестве значения свойства text возвращается текстовая строка, сформированная на основе символьного массива symbs. Чтобы сформировать текст на основе символьного массива, мы создаем анонимный объект класса String и передаем ему аргументом ссылку на символьный массив.
return new string(symbs);
Свойство length описано таким образом, что для объекта класса Alpha оно возвращает длину символьного массива symbs, а для объекта класса Bravo значение свойства определяется количеством символов в текстовом поле txt. Наконец, индексатор описан так, что результатом возвращается символьное значение элемента массива (класс Alpha) или символ из текста (класс Bravo).
В главном методе программы сначала командой
Base obj = new Alpha("Alpha");
Console.WriteLine(obj.text);
obj.text = "Base";
obj = new Bravo("Bravo");
obj.text = "Base";
Console.WriteLine(obj.text);
Что мы получили в данном случае? На основе абстрактного класса мы создали два класса с одинаковым набором характеристик (имеются в виду свойства и индексатор), но при этом "механизм" реализации производных классов разный. Получается, что абстрактный класс задал некоторый шаблон, в соответствии с которым реализованы производные классы. Такой подход на практике нередко оказывается полезным.
На следующем шаге мы начнем знакомиться с интерфейсами.