Dziedziczenie — typy pochodne w celu utworzenia bardziej wyspecjalizowanego zachowania

Dziedziczenie wraz z hermetyzacją i polimorfizmem jest jedną z trzech podstawowych cech programowania obiektowego. Dziedziczenie umożliwia tworzenie nowych klas, które ponownie, rozszerzają i modyfikują zachowanie zdefiniowane w innych klasach. Klasa, której składowe są dziedziczone, jest nazywana klasą bazową, a klasa dziedziczące te składowe jest nazywana klasą pochodną. Klasa pochodna może mieć tylko jedną bezpośrednią klasę bazową. Dziedziczenie jest jednak przechodnie. Jeśli ClassC pochodzi z ClassBelementu i ClassB pochodzi z ClassAelementu , ClassC dziedziczy elementy członkowskie zadeklarowane w ClassB elementach i ClassA.

Uwaga

Struktury nie obsługują dziedziczenia, ale mogą implementować interfejsy.

Koncepcyjnie klasa pochodna jest specjalizacją klasy bazowej. Jeśli na przykład masz klasę Animalbazową , może istnieć jedna klasa pochodna o nazwie i inna klasa pochodna o nazwie MammalReptile. Element Mammal to , a Reptile jest klasą AnimalAnimal, ale każda klasa pochodna reprezentuje różne specjalizacje klasy bazowej.

Deklaracje interfejsu mogą definiować domyślną implementację dla swoich elementów członkowskich. Te implementacje są dziedziczone przez interfejsy pochodne i klasy implementujące te interfejsy. Aby uzyskać więcej informacji na temat domyślnych metod interfejsu, zobacz artykuł dotyczący interfejsów.

Podczas definiowania klasy, która ma pochodzić z innej klasy, klasa pochodna niejawnie uzyskuje wszystkie elementy członkowskie klasy bazowej, z wyjątkiem konstruktorów i finalizatorów. Klasa pochodna ponownie używa kodu w klasie bazowej bez konieczności ponownego jej implementowania. W klasie pochodnej można dodać więcej elementów członkowskich. Klasa pochodna rozszerza funkcjonalność klasy bazowej.

Na poniższej ilustracji przedstawiono klasę WorkItem reprezentującą element pracy w niektórych procesach biznesowych. Podobnie jak wszystkie klasy, pochodzi z System.Object i dziedziczy wszystkie jego metody. WorkItem dodaje sześciu członków własnych. Te elementy członkowskie obejmują konstruktor, ponieważ konstruktory nie są dziedziczone. Klasa ChangeRequest dziedziczy z WorkItem elementu roboczego i reprezentuje określony rodzaj elementu roboczego. ChangeRequestdodaje dwa kolejne elementy członkowskie do elementów członkowskich, z których dziedziczy element WorkItem i z .Object Musi dodać własny konstruktor, a także dodaje originalItemIDelement . Właściwość originalItemID umożliwia skojarzenie ChangeRequest wystąpienia z oryginałem WorkItem , do którego ma zastosowanie żądanie zmiany.

Diagram that shows class inheritance

W poniższym przykładzie pokazano, jak relacje klas pokazane na poprzedniej ilustracji są wyrażane w języku C#. W przykładzie pokazano również, jak WorkItem zastępuje metodę Object.ToStringwirtualną i jak ChangeRequest klasa dziedziczy WorkItem implementację metody . Pierwszy blok definiuje klasy:

// 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;
    }
}

W następnym bloku pokazano, jak używać klas bazowych i pochodnych:

// 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
*/

Metody abstrakcyjne i wirtualne

Gdy klasa bazowa deklaruje metodę jako virtual, klasa pochodna może override metodę z własną implementacją. Jeśli klasa bazowa deklaruje element członkowski jako abstract, ta metoda musi zostać zastąpiona w dowolnej klasie nieklaseksowej, która bezpośrednio dziedziczy z tej klasy. Jeśli klasa pochodna jest sama abstrakcyjna, dziedziczy abstrakcyjne elementy członkowskie bez ich implementowania. Elementy abstrakcyjne i wirtualne są podstawą polimorfizmu, który jest drugą podstawową cechą programowania obiektowego. Aby uzyskać więcej informacji, zobacz Polymorphism (Polimorfizm).

Abstrakcyjne klasy bazowe

Możesz zadeklarować klasę jako abstrakcyjną , jeśli chcesz zapobiec bezpośredniemu utworzeniu wystąpienia przy użyciu nowego operatora. Klasę abstrakcyjną można używać tylko wtedy, gdy nowa klasa pochodzi z niej. Klasa abstrakcyjna może zawierać co najmniej jeden podpis metody zadeklarowany jako abstrakcyjny. Te podpisy określają parametry i wartość zwracaną, ale nie mają implementacji (treść metody). Klasa abstrakcyjna nie musi zawierać abstrakcyjnych składowych; jednak jeśli klasa zawiera abstrakcyjną składową, sama klasa musi być zadeklarowana jako abstrakcyjna. Klasy pochodne, które nie są abstrakcyjne, muszą zapewnić implementację dla żadnych metod abstrakcyjnych z abstrakcyjnej klasy bazowej.

Interfejsy

Interfejs to typ odwołania, który definiuje zestaw elementów członkowskich. Wszystkie klasy i struktury, które implementują ten interfejs, muszą implementować ten zestaw elementów członkowskich. Interfejs może zdefiniować domyślną implementację dla każdego lub wszystkich tych elementów członkowskich. Klasa może implementować wiele interfejsów, mimo że może pochodzić tylko z jednej bezpośredniej klasy bazowej.

Interfejsy służą do definiowania określonych możliwości dla klas, które nie muszą mieć relacji "is a". Na przykład interfejs może być implementowany przez dowolną klasę lub strukturę, aby określić, System.IEquatable<T> czy dwa obiekty typu są równoważne (jednak typ definiuje równoważność). IEquatable<T>nie oznacza tego samego rodzaju relacji "is a", która istnieje między klasą bazową a klasą pochodną Animal(na przykład jest Mammal to ). Aby uzyskać więcej informacji, zobacz Interfejsy.

Zapobieganie dalszemu wyprowadzaniu

Klasa może uniemożliwić dziedziczenie innych klas z niej lub z dowolnego z jej elementów członkowskich, deklarując się lub element członkowski jako sealed.

Klasa pochodna ukrywająca składowe klasy bazowej

Klasa pochodna może ukrywać składowe klasy bazowej, deklarując elementy członkowskie o tej samej nazwie i podpisie. Modyfikator new może służyć do jawnego wskazania, że element członkowski nie jest przeznaczony do zastąpienia elementu członkowskiego podstawowego. Użycie elementu new nie jest wymagane, ale jeśli nie zostanie użyte, zostanie wygenerowane new ostrzeżenie kompilatora. Aby uzyskać więcej informacji, zobacz Przechowywanie wersji za pomocą przesłonięć i nowych słów kluczowych oraz Znajomość, kiedy należy użyć przesłonięć i nowych słów kluczowych.