C#. Наследование. Использование конструкторов в классах при наследовании. Ключевое слово base. Примеры

Наследование. Использование конструкторов в классах при наследовании. Ключевое слово base. Примеры


Содержание


1. Порядок вызова конструкторов классов, которые образовывают иерархию

При создании экземпляра (объекта) класса первым вызывается конструктор этого класса. Если класс B есть производным от базового класса A, то сначала вызывается конструктор базового класса A, а затем конструктор производного класса B. Если классы образовывают иерархию из нескольких уровней наследования, то сначала будут вызываться конструкторы классов высших уровней в виде цепочки вплоть до вызова конструкторов более низких уровней.

На рисунке 1 изображена последовательность вызова конструкторов в случае наследования трех классов с именами A, B, C. Как видно из рисунка, первым всегда вызывается конструктор базового класса A. Вторым всегда вызывается конструктор класса B, который размещается на среднем уровне иерархии. Последним будет вызван конструктор класса C, который находится на нижнем уровне иерархии.

C#. Порядок вызова конструкторов в случае наследования трех классов

Рисунок 1. Демонстрация порядка вызова конструкторов в случае наследования трех классов

Приведенный на рисунке 1 порядок вызова конструкторов есть логически правильным. Это связано с тем, что производные классы имеют более специализированный характер, чем базовый класс. Специализированная часть предусматривает, что она накладывается поверх общей. Таким образом, конструктор производного класса имеет возможность использовать унаследованные качества конструктора базового класса.

 

2. Ключевое слово base. Необходимость применения. Общая форма

Если классы образовывают иерархию, то в производном классе возникает необходимость обратиться к элементу базового класса. В языке C# для таких случаев предусмотрено использование ключевого слова base. Если в базовом классе есть скрытые элементы (объявленные как private), то к этим элементам нет доступа с помощью ключевого слова base.

Общая форма использования ключевого слова base в методе производного класса, следующая:

base.class_element

где

  • class_element – элемент базового класса, который не является скрытым (перед которым не стоит модификатор доступа private). Элементом базового класса может быть конструктор, метод (функция), свойство, поле данных. Для любого из этих элементов предусмотрен отдельный синтаксис использования (смотрите пункты ниже).

 

3. Вызов конструктора базового класса с использованием средства base. Общая форма. Примеры

Конструктор базового класса может быть вызван явным образом из конструктора производного класса. Для этого, при объявлении конструктора производного класса, нужно использовать ключевое слово base. Общая форма такого объявления следующая:

class ClassName
{
  // ...

  // Конструктор класса
  ClassName(parameters2) : base(parameters1)
  {
    // тело конструктора
    // ...
  }
}

где

  • ClassName – имя конструктора производного класса;
  • parameters1 – параметры конструктора базового класса. Чтобы компилятор не выдавал ошибки, в базовом классе должен быть явно объявлен конструктор с точно такими же параметрами;
  • parameters2 – параметры конструктора производного класса.

В строке

...

ClassName(parameters2) : base(parameters1)
{
  // тело конструктора
  // ...
}

...

первым вызовется конструктор базового класса с помощью вызова

base(parameters1)

затем вызывается тело конструктора ClassName().

 



3.1. Пример вызова конструктора базового класса с помощью ключевого слова base. Используется иерархия из трех классов

Пример демонстрирует наследование трех классов A, B, C. Класс A есть базовым для класса B. В свою очередь, класс B есть базовым для класса C.

using System;
using static System.Console;

namespace ConsoleApp1
{
  // Базовый класс A
  class A
  {
    public int a;

    // Конструктор класса A - с одним параметром
    public A(int _a)
    {
      a = _a;
      WriteLine("Constructor A(int): a = {0}", a);
    }
  }

  // Класс B - производный от класса A
  class B : A
  {
    public int b;

    // Конструктор класса B с 1 параметром,
    // неявно вызывает конструктор A() класса A
    public B(int _b) : base(0)
    {
      b = _b;
      WriteLine("Constructor B(int): b = {0}", b);
    }
  }

  // Класс C - производный от класса B
  class C : B
  {
    public int c;

    public C(int _c) : base(0)
    {
      c = _c;
      WriteLine("Constructor C(int): c = {0}", c);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // 1. При создании экземпляра objB
      // конструкторы вызываются в следующей последовательности: A(int)=>B(int)
      B objB = new B(4);
      WriteLine("------");

      // 2. При создании экземпляра objC
      // последовательность вызова конструкторов следующая: A(int)=>B(int)=>C(int)
      C objC = new C(5);
    }
  }
}

Как видно из вышеприведенного кода, В классе из конструктора класса B вызывется конструктор класса A с помощью строки

...

public B(int _b) : base(0) // base(0) => A(0)
{
  ...
}

...

Аналогично в классе C из конструктора C(int) вызывается конструктор класса B

...

public C(int _c) : base(0) // base(0) => B(0)
{
  ...
}

...

Результат работы программы

Constructor A(int): a = 0
Constructor B(int): b = 4
------
Constructor A(int): a = 0
Constructor B(int): b = 0
Constructor C(int): c = 5

 

3.2. Вызов конструктора базового класса с помощью base. Пример классов Human=>Worker

Задан базовый класс Human (Человек). В классе заданы следующие элементы:

  • внутреннее поле name, реализующее фамилию и имя человека;
  • конструктор с 1 параметром Human(string);
  • свойство Name. Используется для доступа к внутреннему полю name.

Из класса Human наследуется класс Worker (Рабочий). В классе Worker реализованы следующие элементы:

  • внутреннее поле – дата принятия на работу date в формате строки;
  • внутреннее поле – должность position;
  • конструктор с тремя параметрами Worker(string, string, string), устанавливающий внутренние поля производного класса Worker и базового класса Human. Для вызова конструктора базового класса используется ключевое слово base;
  • свойство Date для доступа к внутреннему полю date;
  • свойство Position для доступа к внутреннему полю position.

Текст программы, созданной по шаблону Console Application следующий:

using System;
using static System.Console;

namespace ConsoleApp1
{
  // Базовый класс Human
  class Human
  {
    // 1. Скрытое внутреннее поле
    private string name; // Имя человека

    // 2. Конструктор с 1 параметром
    public Human(string _name)
    {
      name = _name;
      WriteLine("Name {0} is set.", name);
    }

    // 3. Свойство Name - необходимо для доступа к name
    public string Name
    {
      get { return name; }
      set { name = value; }
    }
  }

  // Класс Worker - производный от класса Human
  class Worker : Human
  {
    // 1. Внутренние поля
    private string date; // Дата принятия на работу
    private string position; // Посада

    // 2. Конструктор с 3 параметрами,
    // вызывает конструктор базового класса Human
    // с помощью вызова base(name).
    // Сначала выполняется конструктор base(name)=>Human(name),
    // затем используется конструктор Worker(...)
    public Worker(string name, string _date, string _position) : base(name)
    {
      date = _date;
      WriteLine("Date {0} is set.", date);
      position = _position;
      WriteLine("Position {0} is set.", position);
    }

    // 3. Свойство Date
    public string Date
    {
      get { return date; }
      set { date = value; }
    }

    // 4. Свойство Position
    public string Position
    {
      get { return position; }
      set { position = value; }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // При объявлении экземпляра класса Worker
      // сначала вызывается конструктор класса Human,
      // затем вызывается конструктор класса Worker.
      Worker wr = new Worker("J. Johansson", "29.02.2020", "Manager");

      WriteLine("---------");
      wr.Name = "K. Night";
      WriteLine("wr.Name = {0}", wr.Name);
      wr.Date = "01.03.2020";
      WriteLine("wr.Date = {0}", wr.Date);
    }
  }
}

Результат работы программы

Name J. Johansson is set.
Date 29.02.2020 is set.
Position Manager is set.
---------
wr.Name = K. Night
wr.Date = 01.03.2020

 

4. В каких случаях не обязательно использовать ключевое слово base для вызова конструктора базового класса? Пример

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

Пример. Даны два класса с именами A, B. В классе A объявлены 2 конструктора:

  • без параметров A();
  • с параметром A(int).

Класс B есть производный от класса A. В классе B, при объявлении конструктора (любого ) не обязательно использовать ключевое слово base, так как в классе A объявлен конструктор без параметров.

using System;
using static System.Console;

namespace ConsoleApp1
{
  // Базовый класс A
  class A
  {
    public int a;

    // Конструктор класса A - без параметров
    public A()
    {
      a = 1;
      WriteLine("Constructor A(): a = {0}", a);
    }

    // Конструктор класса A - с одним параметром
    public A(int _a)
    {
      a = _a;
      WriteLine("Constructor A(int): a = {0}", a);
    }
  }

  // Класс B - производный от класса A
  class B : A
  {
    public int b;

    // Конструктор класса B без параметров,
    // неявно вызывает конструктор A() класса A
    public B()
    {
      b = 2;
      WriteLine("Constructor B(): b = {0}", b);
    }

    // Конструктор класса B с 1 параметром,
    // неявно вызывает конструктор A() класса A
    public B(int _b)
    {
      b = _b;
      WriteLine("Constructor B(int): b = {0}", b);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // 1. При создании экземпляра objB
      // конструкторы вызываются в следующей последовательности: A()=>B()
      B objB = new B();
      WriteLine("------");

      // 2. При создании экземпляра objB2
      // последовательность вызова конструкторов следующая: A()=>B(int)
      B objB2 = new B(5);
    }
  }
}

Если в классе A() убрать объявление конструктора без параметров

public A()
{
  // ...
}

то компилятор выдаст ошибку

There is no argument given that corresponds to the required formal parameter _a of A.A(int)

 

5. Пример явного вызова конструктора базового класса с помощью ключевого слова base без указания параметров

Если в базовом классе A объявлен конструктор без параметров A(), то допускается указание ключевого слова base без параметров.

Например.

// Базовый класс A
class A
{
  // ...

  // Конструктор класса A - без параметров
  public A()
  {
    // ...
  }

  // ...
}

// Класс B - производный от класса A
class B : A
{
  // ...

  // Конструктор класса B без параметров,
  // явным образом вызывает конструктор A() класса A
  public B() : base() // так тоже можно
  {
    // ...
  }

  // ...
}

 

6. Неявный вызов конструкторов базовых классов на примере иерархии из трех классов.

Пример демонстрирует иерархию вызовов конструкторов без ключевого слова base для классов A, B, C. Ключевое слово base используется неявно.

using System;
using static System.Console;

namespace ConsoleApp1
{
  // Базовый класс A
  class A
  {
    public int a;

    // Конструктор класса A
    public A()
    {
      a = 1;
      WriteLine("Constructor A(): a = {0}", a);
    }
  }

  // Класс B - производный от класса A
  class B : A
  {
    public int b;

    // Конструктор класса B,
    // неявно вызывает конструктор класса A
    public B()
    {
      b = 2;
      WriteLine("Constructor B(): b = {0}", b);
    }
  }

  // Класс C - производный от класса B
  class C : B
  {
    public int c;

    // Конструктор класса C,
    // Неявно вызывает конструктор класса B
    public C()
    {
      c = 3;
      WriteLine("Constructor C(): c = {0}", c);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // 1. При создании экземпляра objB
      // конструкторы вызываются в следующей последовательности: A()=>B()
      B objB = new B();
      WriteLine("------");

      // 2. При создании экземпляра objC
      // последовательность вызова конструкторов: A()=>B()=>C()
      C objC = new C();
      WriteLine("------");
    }
  }
}

Результат выполнения программы

Constructor A(): a = 1
Constructor B(): b = 2
------
Constructor A(): a = 1
Constructor B(): b = 2
Constructor C(): c = 3
------

 


Связанные темы