Введение в ООП с примерами на C#. Часть вторая. Все, что нужно знать о наследовании
23К открытий24К показов
Рассказывает Akhil Mittal
Вступление
В первой статье этой серии мы рассматривали работу разных вариантов реализации перегрузки. В этой части мы сосредоточимся на таком разделе объектно-ориентированного программирования, как наследование.
Давайте сразу тезисно опишем, что такое наследование:
- Это механизм создания нового класса на основе уже существующего старого.
- Старый класс называется «родительским», «предком» («super class»).
- Новый класс называется «дочерним», «наследником» («sub class»).
- Наследование нужно для повторного использования кода, которое облегчает следование принципу DRY (Don’t Repeat Yourself — Не повторяйся).
- Дочерний класс содержит методы и переменные родительского.
Рассмотрим наследование в действии
Создайте консольное приложение и назовите его InheritanceAndPolymorphism
. Добавьте два класса, с названиями ClassA
и ClassB
, как показано ниже:
Как вы можете видеть, класс A пуст, а в B мы добавили два метода и переменную x
со значением 100.
Теперь в главном методе Program.cs
напишите следующее:
Разумеется, этот код вызовет ошибку:
Error: ‘InheritanceAndPolymorphism.ClassA’ does not contain a definition for ‘Display1’ and no extension method ‘Display1’ accepting a first argument of type ‘InheritanceAndPolymorphism.ClassA’ could be found (are you missing a using directive or an assembly reference?)
Очевидно, причина в том, что в классе А нет метода, который мы вызываем. Однако он есть у класса B. Было бы здорово, если бы мы могли получить доступ ко всему коду в B из A!
Теперь измените описание первого класса на следующее:
Теперь после выполнения программы мы получим:
ClassB Display1
Т.е. теперь ClassA
наследует публичные методы из ClassB
, это то же самое, если бы мы скопировали весь код из B в A. Всё, что объект класса B может делать, может и объект класса A. ClassA
— дочерний класс, а ClassB
— родительский.
Что нужно запомнить: как сын получается похожим на отца, наследует его черты, так и дочерний класс имеет параметры родительского.
Теперь давайте представим, что ClassA
тоже имеет метод Display1
:
Что будет, если мы запустим код теперь? Каким будет вывод? И будет ли вывод вообще или выйдет ошибка компиляции? Давайте проверим.
ClassA Display1
Однако мы также получим предупреждение:
Warning: ‘InheritanceAndPolymorphism.ClassA.Display1()’ hides inherited member ‘InheritanceAndPolymorphism.ClassB.Display1()’. Use the new keyword if hiding was intended.
Что нужно запомнить: ничто не может помешать создать в дочернем классе такой же метод, как и в родительском.
Когда мы вызываем a.Display1()
, C# сначала ищет Display1()
в ClassA
, а только потом в ClassB. Поскольку в A такой метод есть, вызывается именно он.
Что нужно запомнить: методы дочерних классов имеют приоритет при выполнении.
Такая возможность нам даётся для того, чтобы мы могли изменить поведение методов предка, если оно нас не устраивает. Однако мы всё равно можем вызывать методы родительского класса следующим образом:
В таком случае вывод будет:
Что нужно запомнить: ключевое слово base
может быть использовано для обращения к методам класса-предка.
Что же, вверх по иерархии мы обращаться можем. Давайте попробуем сделать наоборот:
Что нужно запомнить: наследование не работает в обратном направлении.
Когда класс A наследуется от B, он получает все его методы и может их использовать. Однако методы, которые были добавлены в A, не загружаются наверх в B, наследование не имеет обратной совместимости. Если попытаться вызвать из класса-родителя метод, который создан в классе-наследнике, вы получите ошибку.
Что нужно запомнить: кроме конструкторов и деструкторов, дочерний класс получает от родителя абсолютно всё.
Если класс С, будет унаследован от класса B, который, в свою очередь, будет унаследован от класса A, то класс C унаследует члены как от класса B, так и от класса A. Это транзитивное свойство наследования. Потомок перенимает все члены родителей и не может исключить какие-либо. Он может «спрятать» их, создав свой метод с тем же именем. Конечно, это никак не повлияет на родительский класс, просто в дочернем метод не будет виден.
Члены класса могут быть двух типов — статический, который принадлежит именно классу, или обычный, который доступен только из реализаций класса (его объектов). Чтобы сделать член статическим мы должны использовать ключевое слово static
.
Если мы не наследуем класс ни от какого другого, подразумевается, что мы наследуем его от класса object
. Это — родитель всех классов, и он единственный не унаследован ни от чего. Таким образом, такой код:
Автоматически воспринимается C# так:
Таким образом, по свойству транзитивности, ClassA
также является наследником object
.
Теперь ещё один момент. Если мы захотим сделать так:
То у нас это не получится:
Заметили словосочетание «special class»? Такие классы нельзя расширять.
Что нужно запомнить: ваши классы не могут быть унаследованы от встроенных классов вроде System.ValueType
, System.Enum
, System.Delegate
, System.Array
и т.д.
И ещё кое-что:
Выше мы описали три класса: ClassW
, ClassX
и ClassY
, который наследуется от первых двух. Теперь попробуем это скомпилировать:
Что ещё нужно запомнить: класс может иметь только одного родителя, множественное наследование в C# не поддерживается (оно поддерживается у интерфейсов, но в этой статье мы о них речи не ведём).
Если мы попробуем обойти это правило таким образом:
То это не пройдёт:
Что нужно запомнить: классы не могут наследоваться циклически (1-й от 2-го, 2-й от 3-го 3-й от 1-го), что, в общем-то, логично.
Операции с объектами
Здесь мы пытаемся приравнять объект от разных классов друг к другу.
Однако у нас это плохо получается. Даже несмотря на то, что они имеют одинаковые поля с одинаковыми значениями. Даже если бы эти поля имели одинаковые названия. C# работает с типами очень чётко — вы не можете приравнять два объекта от двух независимых классов. Однако, если бы класс A наследовался от B:
…мы бы продвинулсь немногим дальше:
Как я уже говорил, C# подходит к вопросам типов очень дотошно. Класс A унаследован от B, значит, имеет все его поля и методы — при назначении переменной типа B объекта типа A проблем не возникает. Однако вы уже знаете, что в обратную сторону это не работает — в классе B нет полей и методов, которые могут быть в A.
Что нужно запомнить: вы можете назначить переменной родительского типа объект дочернего, но не наоборот.
Здесь нам наконец-то представляется шанс обмануть правило:
Приведение типа здесь сработает, но только потому, что эти классы находятся в наследственных отношениях. Два обособленных непримитивных типа привести друг к другу нельзя.
Итак, наш последний блок кода:
Что нужно запомнить: можно конвертировать char
в int
. Нельзя конвертировать int
в char
(причина в том, что диапазон целого числа больше, чем символа).
Заключение
В этой части мы рассмотрели наследование. Мы попробовали запускать разные варианты кода, чтобы возможно глубже понять суть этого принципа. *этот текст будет изменён после перевода следующей статьи* In my next article, we’ll be discussing about run time polymorphism. Inheritance plays a very important role in run time polymorphism.
Вот что вы должны были запомнить за сегодня:
- как сын получается похожим на отца, наследует его черты, так и дочерний класс имеет параметры родительского;
- ничто не может помешать создать в дочернем классе такой же метод, как и в родительском;
- методы дочерних классов имеют приоритет при выполнении;
- ключевое слово
base
может быть использовано для обращения к методам класса-предка; - наследование не работает в обратном направлении;
- кроме конструкторов и деструкторов, дочерний класс получает от родителя абсолютно всё;
- ваши классы не могут быть унаследованы от встроенных классов вроде
System.ValueType
,System.Enum
,System.Delegate
,System.Array
и т.д.; - класс может иметь только одного родителя, множественное наследование классов в C# не поддерживается;
- классы не могут наследоваться циклически (1-й от 2-го, 2-й от 3-го 3-й от 1-го), это невозможно чисто логически;
- вы можете назначить переменной родительского типа объект дочернего, но не наоборот;
- можно конвертировать
char
вint
. Нельзя конвертироватьint
вchar
(причина в том, что диапазон целого числа больше, чем символа).
Напоминаем вам, что в первой статье этой серии вы можете прочитать о полиморфизме. Продолжайте учиться программировать с нами!
23К открытий24К показов