Наследование в C#

Наследование в C#

Всем доброго времени суток. На связи Алексей Гулынин. В прошлой статье вы узнали немного о том, что такое делегаты в C#. В данной статье я бы хотел рассказать про наследование в C#. Простыми словами, "Наследование" означает то, что мы создаём класс на основе другого. Получается, у нас есть родительский класс и дочерний класс, который наследует все поля и методы родительского класса. Для того, чтобы понять, что такое наследование и для чего его нужно использовать, вернёмся к понятию объекта.

Объект представляет собой некую абстрактную сущность. Допустим мы хотим создать класс "Animal" (Животное). Но животных же существует великое множество и вряд ли мы одним классом сможем описать их всех. В классе "Animal" мы можем создать поля и методы, присущие всем животным. Например, полями общими для всех животных могут быть "Вес", "Средняя продолжительность жизни", "Имеется хвост или нет". Методом может быть "eat()" (кушать), ведь все же животные питаются. От такого общего класса мы можем создать дочерний класс, который расширяет родительский класс. Например, класс "Dog" (собака) может расширять класс "Animal" уже конкретными полями и методами, которые соответствуют именно собакам.

Отношение наследования — это отношение перехода от более общей абстракции к более конкретной.

В C# наследование является одиночным, то есть нельзя наследоваться от двух и более классов. Наследование определяется через ":".

Интересный момент: если вы пишете обычный класс, который не имеет родителя, то, по умолчанию, этот класс является наследником класса "Object". Класс "Object" является родителем абсолютно для всех классов в .NET.

Немного поговорим про понятия, так или иначе связанные с наследованием.

Абстрактный класс — это класс, объекты которого нельзя создавать, т.е. нельзя будет использовать ключевое слово "new". Абстрактные классы используются при наследовании и используются как прародители к другим классам (реальным, не абстрактным). Т.е. в нашем примере можно класс "Animal" пометить как "abstract". Абстрактным может быть также и метод (это метод без реализации). Если в классе присутствует хотя бы один абстрактный метод, то и сам класс обязан быть абстрактным. В обратную сторону правило не действует.

С помощью ключевого слова "sealed" можно запретить создавать наследников от класса. Пример: класс "String". От этого класса мы не сможем создать наследников. Применение ключевого слова "sealed" к методу означает, что мы запрещаем переопределение этого метода в классах-наследниках.

Ключевое слово "virtual" применяется только к методам, и используется для того, чтобы превратить метод в виртуальный. Это делается для того, чтобы метод можно было переопределить в классах-наследниках. Переопределение метода означает, что мы внутри класса-наследника создаём метод, у которого заголовок полностью совпадает с заголовком метода класса-родителя. При этом в классе-наследнике нужно указать ключевое слово "override", чтобы явно указать, что мы переопределяем метод.

Давайте на примере разберем всё то, что мы только что узнали. Для простоты все поля и методы сделаем публичными (в реальных программах так, конечно, делать не нужно):

namespace TestApplicationForStudy
{
    class Program
    {
        static void Main(string[] args)
        {
            // Так писать нельзя
            // т.к. Animal - абстрактный класс
            // Animal animal = new Animal();
            Dog dog = new Dog();
            dog.name = "Bobik";
            dog.eat();
            dog.bark();
            dog.move();
            Console.ReadLine();
        }

    }
    public abstract class Animal
    {
        public string name; // Пусть у животного будет имя
        public double averageAge; // Средний возраст
        public double weight; // Вес
        public bool hasTale = true; // Имеется ли хвост, по умолчанию - да

        // Создадим виртуальный метод, который переопределим в дочернем классе
        public virtual void eat(){}

        // Обычный метод
        public void move()
        {
            Console.WriteLine("Животное двигается!!!");
        }
    }
    // Класс "Собака"
    public class Dog : Animal
    {
        // Добавим новое поле собаке: paws (лапы)
        public int paws = 4;
        // Добавим метод bark() (лаять)
        public void bark()
        {
            // Поле name пришло из родительского класса Animal
            Console.WriteLine("Собака {0} лает!!!", this.name);
        }
        // Переопределим метод eat()
        // Сделаем также, чтобы его нельзя было переопределить
        sealed public override void eat()
        {
            Console.WriteLine("Собака {0} кушает!!!", this.name);
        }
    }
    // Создадим класс PitBul
    public class PitBul : Dog
    {
        // Попробуем переопределить метод eat(). Получим ошибку:
        // PitBul.eat()': cannot override inherited member 'TestApplicationForStudy.Dog.eat()
        /*public override void eat()
        {
            Console.WriteLine("Питбуль кушает!!!");
        }*/
    }
}

Конструкторы при наследовании.

Конструкторы не наследуются. Если в родительском классе определены различные конструкторы, то при наследовании эти конструкторы будут недоступны у класса-наследника. Несмотря на то, что конструкторы не наследуются — они автоматически вызываются. Когда вызывается конструктор класса-наследника автоматом вызывается конструктор класса-родителя. По умолчанию будет вызываться дефолтный конструктор класса-наследника без параметров. Обращаю ваше внимание на то, что если такого конструктора без параметров не будет, то будет получена ошибка при компиляции.

Если мы хотим, чтобы вызывался другой родительский конструктор, то это можно указать с помощью ключевого слова "base".

С помощью "base" также можно вызывать родительский метод.

Разберем на эту тему вот такой пример: пусть имеется родительский класс "Degree", имеющий одной поле "degrees" и один метод, который возвращает значение данного поля. Создадим дочерний класс "Radiance", который, используя метод родительского класса, возвращает градусы, переведенные в радианы:

static void Main(string[] args)
{
		Radiance radiance = new Radiance();
		Console.WriteLine(radiance.getValues());
		Console.ReadLine();
}
class Degree
{
   private double degrees = 60;
   public virtual double getValues()
   {
      return this.degrees;
   }
}
class Radiance : Degree
{
   public override double getValues()
   {
      // Вызываем метод getValues() родительского класса Degree
      return (base.getValues() * Math.PI / 180);
   }     
}

Напоследок, обобщу особенности наследования:

  • Ключевые слова "sealed" и "static" (статический класс, про него поговорим в отдельной статье) запрещают наследование
  • Если в базовом классе определен какой-то метод "abstract", то базовый класс тоже должен быть абстрактным. В классе-наследнике такой абстрактный метод нужно переопределить. Абстрактный метод, по умолчанию, является виртуальным.
  • При проектировании программы важным является понимание того, что от чего можно унаследовать, а что нельзя. Для проверки условия наследования используется слово "является". В нашем примере: "Собака является животным? — является", "Питбуль является собакой? — является". А вот наоборот лучше не делать (технически конечно можно, но программы лучше сразу проектировать правильно), "Животное является собакой? — не является". Поэтому класс "Animal" от класса "Dog" наследовать нельзя.

В данной статье вы узнали про механизм наследования в C# и о том, как его лучше использовать.

На связи был Алексей Гулынин, оставляйте свои комментарии, увидимся в следующих статьях.


Комментарии:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *