Наследование в C#
Наследование позволяет создать обобщенный класс, в котором определены общие характерные особенности, присущие некоторому множеству элементов. От этого класса могут затем наследовать другие классы, добавляя в него свои индивидуальные особенности. Такой подход позволяет:
1. Упростить процесс программирования, поскольку реализацию новых классов можно выполнять на основе уже существующих.
2. Реализовать полиморфизм, под которым понимается возможность в процессе выполнения приложения выбирать разные алгоритмы реализации команды.
3. Повысить надежность программ, уменьшить трудоемкость разработки новых приложений.
Класс, на основе которого создается новый класс, называется базовым, а вновь создаваемый класс называется производным. Базовый класс определяет все те элементы, которые будут общими для всех производных классов, полученных из этого базового класса. Производный класс наследует эти общие элементы и добавляет новые члены, характерные для данного класса.
Например, в системе Windows определен базовый класс Control, который определяет все доступные элементы управления (кнопки, диалоги, списки и т. д.), и обработчики событий для каждого (действие при щелчке по кнопке, выбор позиции и т. д.). На основании этого базового класса строится все многообразие элементов управления, используемых в приложениях.
Производный класс не может быть наследником от нескольких базовых классов. Но наследование может быть последовательным. Для класса GenClass можно оформить производный ClassA, от которого может наследовать ClassB. Таким образом ClassB наследует члены, объявленные как в GenClass, так и в ClassA.
Существует два вида наследования: наследование реализации и наследование интерфейса.
1. При наследовании реализации (implementation inheritance) производный класс является наследником базового типа, получая доступ ко всем данным и методам (с учетом разграничения доступа). Реализация функций в производном классе может быть переопределена. Такой вид наследования позволяет добавить дополнительные подпрограммы к существующему классу и предоставляет возможность совместного использования общей функциональности нескольких классов.
2. При наследовании интерфейса (interface inheritance) производный класс наследует только сигнатуру функций без их реализации. Реализация функций выполнена в базовом классе, который обеспечивает возможность обращения к к этим функциям из производного класса. Такие базовые классы называются интерфейсами, их имена начинаются с заглавной буквы I (например, IEnumerable, IComparable и т. д.). Они содержат готовые подпрограммы для реализации часто применяемых действий.
Для указания на наследование после имени класса записывают двоеточие, после которого указывают класс, от которого выполняется наследование. Формат записи наследования:
class newClass : BaseClass
{
<элементы, добавляемые к классу NewClass>
}
Указанные в примере имена newClass и BaseClass, естественно, могут быть заменены любыми другими.
При наследовании уровни доступа public и private сохраняют свой обычный смысл, но добавлен уровень доступа protected, который применяется при наследовании для доступности элементов базового класса в производном классе.
Следует иметь в виду простой принцип: базовый класс «ничего не знает» о производном классе, но производный класс «знает» только те элементы базового класса, которые для него доступны.
Уровень защиты protected обеспечивает доступ к членам базового класса из его наследников, но не разрешает доступ к этим членам из посторонних классов.
Производному классу всегда доступны члены базового класса с уровнями защиты public и protected и недоступны члены с уровнем защиты private.
Объявленные в базовом классе доступные члены рассматриваются как глобальные в производных классах. Если такие элементы не переопределяются и не перегружаются в производном классе, то их используют в производном классе без указания видимости. Если же переопределение или перегрузка имеет место (например, когда в базовом и производном классах имеются подпрограммы с одинаковыми сигнатурами, но реализующих разные действия), то перед именем подпрограммы следует указывать видимость (точка после имени класса и после этого имя переменной или подпрограммы). Это однозначно определяет, к какому из перегруженных методов выполняется обращение.
В языке С# не предусмотрено одновременное наследование одного производного класса от нескольких базовых классов, поэтому для любого производного класса можно задать в качестве базового только один класс. Исключение составляют интерфейсы, которые допускают множественное наследование. Множественное наследование предусматривает запись нескольких имен классов в виде списка через запятую.
У одного базового класса может быть несколько производных классов. Кроме того, любой производный класс может быть базовым для другого производного класса. Таким образом, можно построить иерархию производных классов, в которой каждый последующий производный класс является наследником предыдущего. Производный класс наследует доступные члены своего базового класса (переменные, подпрограммы, аксессоры, индексаторы и т. д.). Эти члены должны быть объявлены с уровнем доступа public или protected. Члены с уровнем доступа private не наследуются.
При необходимости наследование для некоторого класса можно запретить, записав перед словом class ключевое слово sealed. Обычно такой запрет применяется для реализации вспомогательных для приложения операций. Такие классы называются изолированными.
При создании в главной программе связки из базового и производного классов, как правило, объявляется объект только производного класса, поскольку базовый класс является частью этой связки. Объекты создаются в последовательности: сначала базовый класс, затем производный.
Компилятор создает конструкторы по умолчанию, которые инициализируют числовые переменные нулями, а для строковых переменных задаются пустые строки. Если оформляются конструкторы с параметрами или явно задаются конструкторы без параметров, то иерархия вызова конструкторов не должна нарушаться: сначала вызываются конструкторы базового класса, а затем конструкторы производного класса. В частности, при попытке оформления конструктора для производного класса выдается сообщение об ошибке, гласящая о том, что в базовом классе отсутствует конструктор без параметров, если таковой не был оформлен.
В конструкторе производного класса можно присваивать значения из базового класса Троелсен стр. 235.
допускается определять тип (перечисление, класс, интерфейс, структуруили делегат) непосредственно внутри контекста класса или структуры. При этом вложенный (или “внутренний”) тип считается членом охватывающего (или “внешнего”) класса, и в глазах исполняющей системы им можно манипулировать как любым другим членом (полем, свойством, методом и событием). Синтаксис, используемый для вложения типа, достаточно прост:
public class OuterClass
{
// Открытый вложенный тип может использоваться повсюду,
public class PublicInnerClass {}
// Закрытый вложенный тип может использоваться только членами включающего класса,
private class PrivateInnerClass {}
}
Хотя синтаксис довольно очевиден, понять, для чего это может потребоваться, не так-то просто. Чтобы разобраться с этой техникой, рассмотрим характерные особенности вложенных типов.
• Вложенные типы позволяют получить полный контроль над уровнем доступа внутреннего типа, поскольку они могут быть объявлены как закрытые (вспомните, что не вложенные классы не могут быть объявлены с использованием ключевого слова private).
• Поскольку вложенный тип является членом включающего класса, он может иметь доступ к закрытым членам включающего класса.
• Часто вложенные типы удобны в качестве вспомогательных для внешнего класса и не предназначены для использования внешним миром.
Когда тип включает в себя другой тип класса, он может создавать переменные-члены этого типа, как любой другой элемент данных. Однако если вложенный тип нужно применять вне включающего типа, его понадобится квалифицировать именем включающего типа. Взгляните на следующий код:
static void Main(string[] args) Троелсен стр. 239
Конструкторы для базового класса и производного класса оформляются каждый в своем классе. Если для создания объекта базового класса требуется вызов конструктора с параметрами (когда базовый класс не объявляют), то передать такие параметры можно через конструктор производного класса. Поэтому конструктор производного класса является как бы наследником конструктора базового класса.
Для передачи через конструктор производного класса параметров для конструктора от базового класса применяется ключевое слово base, после которого в круглых скобках перечисляются параметры, передаваемые в конструктор базового класса. Ключевое слово base записывается после двоеточия после закрывающей скобки конструктора производного класса.
Базовый класс и производный могут иметь конструкторы как каждый из них отдельно, так и вместе. Если конструкторы имеются в обоих классах, то порядок их вызова совпадает с последовательностью наследования, то есть конструкторы базового класса выполняются раньше конструкторов производного класса, поскольку базовый класс "не знает" о существовании производного класса. Форма записи конструктора (из примера приведенной ниже программы) имеет вид:
MyDeriv (double sum,double rate, double per) : base (sum)
{
this.saving = sum; // Инициализация члена базового класса из производного
Здесь параметры rate и per передаются в конструктор класса MyDeriv, а параметр sum предназначен для конструктора класса Bank. Обратите внимание на дублирование параметра sum в первом (после MyDeriv) и втором (после base) списках, передаваемого для конструктора базового класса. Тип параметров в скобках после слова base не указываются. Это значит, что значение (в данном примере для sum) передается в списке параметров производного класса, а запись указывает на принадлежность параметра базовому классу.
В литературе говорится о том, что конструктор производного класса «вызывает» конструктор базового класса, но на самом деле это не совсем точно. Поскольку после объявления производного класса оба класса являются единым целым, то в конструкторе производного класса выполняется инициализация члена базового класса, минуя конструктор базового класса, как показано выше.
Одна и та же переменная, являющаяся членом класса, может появиться в трех местах. Везде эта переменная может иметь как разные имена, так и одно и то же.
1. При объявлении члена класса.
2. Как член списка конструктора с параметрами. Если в списке параметров конструктора имя переменной отличается от имени, объявленного в классе, то соответствие устанавливается с помощью оператора присваивания:
3. Как член списка параметров конструктора при объявлении объекта класса. В общем случае конструктор вызывается по сигнатуре.
В языке C# можно в качестве имен параметров конструктора можно использовать имена членов класса. Тогда в коде конструктора эти имена могут применяться напрямую, без промежуточного присваивания (см. примечания в тексте программы).
Примечание: Промежуточное присваивание требуется, когда инициализируется элемент базового класса в конструкторе производного класса и имена переменных в конструкторе и при объявлении не совпадают.
Имеется также возможность связать имена параметров конструктора с именами переменных в основной программе. Для этого в списке параметров при объявлении объекта класса записывается имя из списка параметров конструктора, а через двоеточие имя переменной в основной программе. Пусть в приведенной ниже программе переменные объявлены следующим образом:
double s, r, p;
Если определить конструктор производного класса:
public CItrst (double saving, double rate, double period) : base (sum)
{
this.rate = rate;
this.per = period;
}
Тогда объявление производного класса можно выполнить так
CItrst ab = new CItrst(s, r, p); // Вызов по сигнатуре
Или так (указаны имена параметров конструктора):
CItrst ab = new CItrst(sum: s, rate: r, period: p);
Ниже в примере показана программа обработки вкладов с добавлением функции начисления процентов на вклад. Класс обеспечивает реализацию действий по добавлению денег на вклад и снятие денег со счета с соответствующими проверками. В программе Main объект базового класса не объявляется вообще, но при объявлении производного класса вызывается конструктор базового класса, который запрашивает ввод начальной суммы. Функция начисления процентов определена в производном классе, который является наследником от базового класса.
Обратите внимание на то, что функция начисления процентов полностью автономна и добавляет эту функциональность к базовому классу, не пересекаясь с ним. Единственная связь заключается в указании уровня доступа protected для переменной saving с целью ее видимости в производном классе.
// Базовый класс
public class CBank
{
protected double saving;
public CBank() // Конструктор базового класса
{
Console.WriteLine(" Введите начальную сумму");
saving = double.Parse(Console.ReadLine());
}//
public void deposit(double amt)
{
saving += amt; // Добавление суммы на счет
Console.WriteLine(" Остаток = {0}", saving);
}
void withdraw(double amt) // Снятие суммы со счета
{
if (amt < saving)
{
saving -= amt;
Console.WriteLine(" Остаток = {0}", saving);
}
else
{
Console.WriteLine(" Сумма на счете = {0 } меньше нуля, операция"+
“ отменена", saving - amt +" Остаток = {0}", saving);
}
}
// Добавление или уменьшение депозита
public void insum()
{
ConsoleKeyInfo ch;//
double sum;
do
{
Console.WriteLine("
Вы хотите изменить сумму?");
Console.WriteLine
(" Добавить - буква Y, уменьшить - буква N, далее - буква E ");
ch = Console.ReadKey(true); // true, чтобы не было на экране эха буквы
if (ch.Key.Equals(ConsoleKey.E)) break; // Выход из подпрограммы
Console.WriteLine(" Введите сумму");
sum = double.Parse(Console.ReadLine());
if (ch.Key.Equals(ConsoleKey.Y)) deposit(sum);
if (ch.Key.Equals(ConsoleKey.N)) withdraw(sum);
} while (true);
}
}
// Объявление производного класса.
class CItrst : CBank
{
public double rate, //Процентная ставка
factor, //Коэффициент увеличения капитала
per; //Количество периодов начисления процентов
public CItrst()
{
insum();// Вызов
Console.WriteLine("
Введите ставку и количество периодов
");
rate = double.Parse(Console.ReadLine());
per = double.Parse(Console.ReadLine());
factor = Math.Pow(1 + rate / 100, per);
}
//Это функция-член класса CItrst для вывода остатка на счете
public void rest()
{
double res = saving;//
Console.WriteLine("
Остаток на счете = " + res * factor);
}
}
class Program
{ // Основная программа
static void Main()
{
CItrst ab = new CItrst();// Базовый класс не объявляется
ab.rest(); //
Console.ReadKey(); // Останов экрана
}
}
При нажатии на клавишу буквы E происходит выход из цикла do-while и тем самым выход из метода insum. Для распечатки результата вызывается метод rest.
Ниже приведен вариант выполнения программы.
Уровень protected обеспечивает доступ только из производного класса в базовый. В примере основная программа (Main) входит в состав постороннего класса, который не является ни базовым, ни производным классом, поэтому для доступа к подпрограммам rest из основной программы требуется уровень public.
Наследование в C#
Лекции по предмету «Программирование»