Наследование (inheritance) является одним из ключевых моментов ООП. Его смысл состоит в том, что мы можем расширить функциональность уже существующих классов за счет добавления нового функционала или изменения старого. Пусть у нас есть следующий класс Person, описывающий отдельного человека:
class Person
{
private string _firstName;
private string _lastName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
public void Display()
{
Console.WriteLine(FirstName + " " + LastName);
}
}
Но вдруг нам потребовался класс, описывающий сотрудника предприятия - класс Employee. Поскольку этот класс будет реализовывать тот же функционал, что и класс Person, так как сотрудник - это также и человек, то было бы рационально сделать класс Employee производным (или наследником, или подклассом) от класса Person, который, в свою очередь, называется базовым классом или родителем (или суперклассом):
|
|
class Employee: Person
{
}
После двоеточия мы указываем базовый класс для данного класса. Для класса Employee базовым является Person, и поэтому класс Employee наследует все те же свойства, методы, поля, которые есть в классе Person. Единственное, что не передается при наследовании, это конструкторы базового класса.
Таким образом, наследование реализует отношение is-a (является), объект класса Employee также является объектом класса Person:
static void Main(string[] args)
{
Person p = new Person { FirstName = "Bill", LastName = "Gates" };
p.Display();
p = new Employee { FirstName = "Denis", LastName = "Ritchi" };
p.Display();
Console.Read();
}
И поскольку объект Employee является также и объектом Person, то мы можем так определить переменную: Person p = new Employee().
Все классы по умолчанию могут наследоваться. Однако здесь есть ряд ограничений:
· Не поддерживается множественное наследование, класс может наследоваться только от одного класса. Хотя проблема множественного наследования реализуется с помощью концепции интерфейсов, о которых мы поговорим позже.
· При создании производного класса надо учитывать тип доступа к базовому классу - тип доступа к производному классу должен быть таким же, как и у базового класса, или более строгим. То есть, если базовый класс у нас имеет тип доступа internal, то производный класс может иметь тип доступа internal или private, но не public.
· Если класс объявлен с модификатором sealed, то от этого класса нельзя наследовать и создавать производные классы.
Например, следующий класс не допускает создание наследников:
sealed class Admin
{
}
Доступ к членам базового класса из класса-наследника
|
|
Вернемся к нашим классам Person и Employee. Хотя Employee наследует весь функционал от класса Person, посмотрим, что будет в следующем случае:
class Employee: Person
{
public void Display()
{
Console.WriteLine(_firstName);
}
}
Этот код не сработает и выдаст ошибку, так как переменная _firstName объявлена с модификатором private и поэтому к ней доступ имеет только класс Person. Но зато в классе Person определено общедоступное свойство FirstName, которое мы можем использовать, поэтому следующий код у нас будет работать нормально:
class Employee: Person
{
public void Display()
{
Console.WriteLine(FirstName);
}
}
Таким образом, производный класс может иметь доступ только к тем членам базового класса, которые определены с модификаторамиpublic, internal, protected и protected internal.
Ключевое слово base
Теперь добавим в наши классы конструкторы:
class Person
{
public string FirstName { get; set; }
public string LastName {get; set; }
public Person(string fName, string lName)
{
FirstName = fName;
LastName = lName;
}
public void Display()
{
Console.WriteLine(FirstName + " " + LastName);
}
}
class Employee: Person
{
public string Company { get; set; }
public Employee(string fName, string lName, string comp)
:base(fName, lName)
{
Company = comp;
}
}
Класс Person имеет стандартный конструктор, который устанавливает два свойства. Поскольку класс Employee наследует и устанавливает те же свойства, что и класс Person, то логично было бы не писать по сто раз код установки, а как-то вызвать соответствующий код класса Person. К тому же свойств, которые надо установить, и параметров может быть гораздо больше.
С помощью ключевого слова base мы можем обратиться к базовому классу. В нашем случае в конструкторе класса Employee нам надо установить имя, фамилию и компанию. Но имя и фамилию мы передаем на установку в конструктор базового класса, то есть в конструктор класса Person, с помощью выражения base(fName, lName).
static void Main(string[] args)
{
Person p = new Person("Bill", "Gates");
p.Display();
Employee emp = new Employee ("Tom", "Simpson", "Microsoft");
emp.Display();
Console.Read();
}