Поделиться через


Наследование — производные типы для создания более специализированного поведения

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

Примечание.

Структуры не поддерживают наследование, но могут реализовывать интерфейсы.

Концептуально производный класс является специализацией базового класса. Например, при наличии базового класса Animal возможно наличие одного производного класса, который называется Mammal, и еще одного производного класса, который называется Reptile. Mammal является Animal и Reptile является Animal, но каждый производный класс представляет разные специализации базового класса.

Объявления интерфейса могут определять реализацию по умолчанию для членов. Эти реализации наследуются производными интерфейсами и классами, реализующими эти интерфейсы. Дополнительные сведения о методах интерфейса по умолчанию см. в статье об интерфейсах.

При определении класса для наследования от другого класса производный класс явно получает все члены базового класса за исключением конструкторов и методов завершения. Производный класс повторно использует код в базовом классе без необходимости его повторной реализации. В производный класс можно добавить дополнительные члены. Производный класс расширяет функциональность базового класса.

На следующем рисунке показан класс WorkItem, представляющий рабочий элемент в бизнес-процессе. Как и другие классы, он является производным от System.Object и наследует все его методы. WorkItem добавляет шесть членов собственного. К ним относится конструктор, так как конструкторы не наследуются. Класс ChangeRequest наследует от WorkItem и представляет конкретный вид рабочего элемента. ChangeRequest добавляет еще два члена к членам, унаследованным от WorkItem и Object. Он должен добавить собственный конструктор, и он также добавляет originalItemID. Свойство originalItemID позволяет ChangeRequest связать экземпляр с исходным объектом WorkItem, к которому применен запрос на изменение.

Схема, показывающая наследование классов

В следующем примере показано, как выражаются в C# отношения между классами, продемонстрированные на предыдущем рисунке. В примере также показано, как WorkItem переопределяет виртуальный метод Object.ToString и как класс ChangeRequest наследует WorkItem реализацию метода. В первом блоке определяются классы:

// WorkItem implicitly inherits from the Object class.
public class WorkItem
{
    // Static field currentID stores the job ID of the last WorkItem that
    // has been created.
    private static int currentID;

    //Properties.
    protected int ID { get; set; }
    protected string Title { get; set; }
    protected string Description { get; set; }
    protected TimeSpan jobLength { get; set; }

    // Default constructor. If a derived class does not invoke a base-
    // class constructor explicitly, the default constructor is called
    // implicitly.
    public WorkItem()
    {
        ID = 0;
        Title = "Default title";
        Description = "Default description.";
        jobLength = new TimeSpan();
    }

    // Instance constructor that has three parameters.
    public WorkItem(string title, string desc, TimeSpan joblen)
    {
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = joblen;
    }

    // Static constructor to initialize the static member, currentID. This
    // constructor is called one time, automatically, before any instance
    // of WorkItem or ChangeRequest is created, or currentID is referenced.
    static WorkItem() => currentID = 0;

    // currentID is a static field. It is incremented each time a new
    // instance of WorkItem is created.
    protected int GetNextID() => ++currentID;

    // Method Update enables you to update the title and job length of an
    // existing WorkItem object.
    public void Update(string title, TimeSpan joblen)
    {
        this.Title = title;
        this.jobLength = joblen;
    }

    // Virtual method override of the ToString method that is inherited
    // from System.Object.
    public override string ToString() =>
        $"{this.ID} - {this.Title}";
}

// ChangeRequest derives from WorkItem and adds a property (originalItemID)
// and two constructors.
public class ChangeRequest : WorkItem
{
    protected int originalItemID { get; set; }

    // Constructors. Because neither constructor calls a base-class
    // constructor explicitly, the default constructor in the base class
    // is called implicitly. The base class must contain a default
    // constructor.

    // Default constructor for the derived class.
    public ChangeRequest() { }

    // Instance constructor that has four parameters.
    public ChangeRequest(string title, string desc, TimeSpan jobLen,
                         int originalID)
    {
        // The following properties and the GetNexID method are inherited
        // from WorkItem.
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = jobLen;

        // Property originalItemID is a member of ChangeRequest, but not
        // of WorkItem.
        this.originalItemID = originalID;
    }
}

В следующем блоке показано, как использовать базовый и производный классы:

// Create an instance of WorkItem by using the constructor in the
// base class that takes three arguments.
WorkItem item = new WorkItem("Fix Bugs",
                            "Fix all bugs in my code branch",
                            new TimeSpan(3, 4, 0, 0));

// Create an instance of ChangeRequest by using the constructor in
// the derived class that takes four arguments.
ChangeRequest change = new ChangeRequest("Change Base Class Design",
                                        "Add members to the class",
                                        new TimeSpan(4, 0, 0),
                                        1);

// Use the ToString method defined in WorkItem.
Console.WriteLine(item.ToString());

// Use the inherited Update method to change the title of the
// ChangeRequest object.
change.Update("Change the Design of the Base Class",
    new TimeSpan(4, 0, 0));

// ChangeRequest inherits WorkItem's override of ToString.
Console.WriteLine(change.ToString());
/* Output:
    1 - Fix Bugs
    2 - Change the Design of the Base Class
*/

Абстрактные и виртуальные методы

Когда базовый класс объявляет метод как virtual, производный класс может override метод с помощью своей собственной реализации. Если базовый класс объявляет член как abstract, этот метод должен быть переопределен в любом неабстрактном классе, который прямо наследует от этого класса. Если производный класс сам является абстрактным, то он наследует абстрактные члены, не реализуя их. Абстрактные и виртуальные члены являются основой для полиморфизма, который является второй основной характеристикой объектно-ориентированного программирования. Дополнительные сведения см. в разделе Полиморфизм.

Абстрактные базовые классы

Можно объявить класс как абстрактный, если необходимо предотвратить прямое создание экземпляров с помощью оператора new. Абстрактный класс можно использовать, только если новый класс является производным от него. Абстрактный класс может содержать один или несколько сигнатур методов, которые сами объявлены в качестве абстрактных. Эти сигнатуры задают параметры и возвращают значение, но не имеют реализации (тела метода). Абстрактному классу необязательно содержать абстрактные члены; однако если класс все же содержит абстрактный член, то сам класс должен быть объявлен в качестве абстрактного. Производные классы, которые сами не являются абстрактными, должны предоставить реализацию для любых абстрактных методов из абстрактного базового класса.

Интерфейсы

Интерфейс — это ссылочный тип, определяющий набор членов. Этот набор членов должны реализовать все классы и структуры, реализующие интерфейс. Интерфейс может определять реализацию по умолчанию для любого из этих членов или для них всех. В классе может быть реализовано несколько интерфейсов, хотя производным он может быть только от одного прямого базового класса.

Интерфейсы используются для определения конкретных возможностей для классов, которые не обязательно имеют отношения тождественности. Например, интерфейс System.IEquatable<T> может быть реализован любым классом или структурой для определения эквивалентности двух объектов типа (однако тип определяет эквивалентность). IEquatable<T> не подразумевает тот же вид отношений тождественности, который существует между базовым и производным классами (например, Mammal является Animal). Дополнительные сведения см. в статье Интерфейсы.

Предотвращение дальнейшего наследования

Класс может предотвратить наследование от других классов или наследование от любых его членов, объявив себя или члены как sealed.

Скрытие производного класса членов базового класса

Производный класс может скрывать члены базового класса путем объявления членов с тем же именем и сигнатурой. Модификатор new может использоваться, чтобы явно указать, что член не должен быть переопределением базового члена. Использовать new необязательно, но если new не используется, будет создано предупреждение компилятора. Дополнительные сведения см. в разделах Управление версиями с помощью ключевых слов Override и New и Использование ключевых слов Override и New.