На этом шаге мы рассмотрим особенности организации доступа к таким переменным.
Ранее практически каждый раз при работе с объектами мы ссылку на объект записывали в объектную переменную того же класса. Механизм наследования вносит в это строгое правило некоторое разнообразие. Дело в том, что объектная переменная базового класса может ссылаться на объект производного. Допустим, имеется базовый класс Base, на основе которого наследованием создается класс MyClass. Если мы создадим на основе класса MyClass объект, то ссылку на этот объект можно записать не только в объектную переменную класса MyClass, но и в объектную переменную базового класса Base. Правда, здесь есть одно очень важное ограничение: через объектную переменную базового класса можно получить доступ только к тем членам производного класса, которые объявлены в базовом классе. В нашем случае это означает, что через объектную переменную класса Base можно получить доступ только к членам объекта производного класса MyClass, объявленным в классе Base.
Даже несмотря на упомянутое ограничение, данная особенность объектных переменных базовых классов является фундаментально важной и проявляется и используется в самых разных (часто неожиданных) ситуациях. Как небольшую иллюстрацию рассмотрим приведенную ниже программу.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace pr143_1 { // Базовый класс: class Base { // Целочисленное поле: public int code; // Открытый метод: public void show() { Console.WriteLine("Поле code: " + code); } // Конструктор с целочисленным аргументом: public Base(int n){ code = n; } // Конструктор создания копии: public Base(Base obj){ code = obj.code; } } // Производный класс: class MyClass: Base { // Символьное поле: public char symb; // Открытый метод: public void display() { Console.WriteLine("Поле symb: " + symb); } // Конструктор с двумя аргументами: public MyClass(int n, char s): base(n){ symb = s; } // Конструктор создания копии: public MyClass(MyClass obj): base(obj) { symb = obj.symb; } } // Класс с главным методом: class Program { // Главный метод: static void Main() { // Создание объекта производного класса: MyClass A = new MyClass(100, 'A'); // Объектная переменная базового класса: Base obj; // Объектной переменной базового класса присваивается // ссылка на объект производного класса: obj = A; Console.WriteLine("Используем переменную obj:"); // Вызов метода через объектную переменную // базового класса: obj.show(); // Использовано явное приведение типа: ((MyClass)obj).display(); // Обращение к полю через объектную переменную // базового класса: obj.code = 200; // Использовано явное приведение типа: ((MyClass)obj).symb = 'В'; Console.WriteLine("Используем переменную А:"); // Вызов методов через объектную переменную // производного класса: A.show(); A.display(); // Создание копии объекта: MyClass B = new MyClass(A); // Изменение значений полей исходного объекта: A.code = 0; A.symb = 'O'; Console.WriteLine("Используем переменную В:"); // Проверка значений полей объекта-копии: B.show(); B.display(); // Задержка: Console.ReadLine(); } } }
Ниже представлен результат выполнения программы.
Рис.1. Результат выполнения программы
В этой программе мы описываем базовый класс Base. У него есть открытое целочисленное поле code, метод show(), который при вызове отображает в консольном окне значение целочисленного поля, а также в классе есть две версии конструктора. Есть конструктор с одним целочисленным аргументом, и еще имеется конструктор создания копии, аргументом которому передается объект класса Base.
В нашем примере поле code создаваемого объекта получает такое же значение, какое есть у поля code объекта, переданного аргументом конструктору (команда
code = obj.code;
На основе класса Base путем наследования создается класс MyClass. В этом классе дополнительно описывается символьное поле symb, метод display(), отображающий в консольном окне значение символьного поля, а также две версии конструктора. В соответствии с описанными версиями объект класса MyClass можно создавать, передав два аргумента конструктору (целое число и символ) или передав аргументом уже существующий объект класса MyClass (конструктор создания копии). Если при создании объекта класса MyClass используются два аргумента, то первый, целочисленный, аргумент передается конструктору базового класса, а второй, символьный, аргумент определяет значение символьного поля. С конструктором создания копии все намного интереснее. Аргумент конструктора obj описан как такой, что является объектной ссылкой класса MyClass. И эта объектная ссылка передается конструктору базового класса (инструкция
base(obj)
Другими словами, есть специальная "рабочая", автоматически создаваемая переменная, в которую копируется аргумент конструктора. Данная переменная относится к классу Base, а передается аргументом переменная класса MyClass. Получается, что в объектную переменную класса Base копируется значение переменной класса MyClass. Но это можно делать, поскольку класс Base является базовым для класса MyClass.
При вызове конструктора базового класса с аргументом, являющимся ссылкой на объект производного класса, доступ есть только к тем полям и методам объекта-аргумента, которые объявлены в базовом классе. Но больше и не нужно. Конструктор базового класса выполняет свою работу и присваивает значение полю code создаваемого объекта. После этого командой
symb = obj.symb;
В главном методе программы командой
MyClass A = new MyClass(100, 'A');
obj = A;
obj.show()
obj.code = 200;
((MyClass)obj).display();
((MyClass)obj).symb = 'В';
После присваивания с использованием переменной obj новых значений полям code и symb, используя переменную А, убеждаемся, что переменные obj и А действительно ссылаются на один и тот же объект.
Командой
MyClass B = new MyClass(A);
А.code = 0;
A.symb = 'O';
В.show();
В.display();
На следующем шаге мы рассмотрим замещение членов при наследовании.