Freigeben über


Vererbung – Ableiten von Typen zum Erstellen von spezielleren Verhaltensweisen

Die Vererbung, zusammen mit Kapselung und Polymorphismus, ist eines der drei Hauptmerkmale der objektorientierten Programmierung. Mit der Vererbung können Sie neue Klassen erstellen, die das in anderen Klassen definierte Verhalten wiederverwenden, erweitern und ändern. Die Klasse, deren Member geerbt werden, wird als Basisklasse bezeichnet, und die Klasse, die diese Member erbt, wird als abgeleitete Klasse bezeichnet. Eine abgeleitete Klasse kann nur eine direkte Basisklasse aufweisen. Die Vererbung ist jedoch transitiv. Wenn ClassC von ClassB abgeleitet ist und ClassB von ClassA abgeleitet ist, dann erbt ClassC die Mitglieder, die in ClassB und ClassA erklärt werden.

Hinweis

Strukturen unterstützen keine Vererbung, aber sie können Schnittstellen implementieren.

Konzeptionell ist eine abgeleitete Klasse eine Spezialisierung der Basisklasse. Wenn Sie beispielsweise über eine Basisklasse Animalverfügen, verfügen Sie möglicherweise über eine abgeleitete Klasse, die benannt Mammal ist, und eine andere abgeleitete Klasse, die benannt Reptilewird. A Mammal ist ein Animal, und eine Reptile ist eine Animal, aber jede abgeleitete Klasse stellt unterschiedliche Spezialisierungen der Basisklasse dar.

Schnittstellendeklarationen können eine Standardimplementierung für ihre Mitglieder definieren. Diese Implementierungen werden von abgeleiteten Schnittstellen und von Klassen geerbt, die diese Schnittstellen implementieren. Weitere Informationen zu Standardschnittstellenmethoden finden Sie im Artikel zu Schnittstellen.

Wenn Sie eine Klasse definieren, die von einer anderen Klasse abgeleitet werden soll, erhält die abgeleitete Klasse implizit alle Member der Basisklasse, mit Ausnahme der Konstruktoren und Finalizer. Die abgeleitete Klasse verwendet den Code in der Basisklasse wieder, ohne dass sie erneut implementiert werden muss. Sie können weitere Member in der abgeleiteten Klasse hinzufügen. Die abgeleitete Klasse erweitert die Funktionalität der Basisklasse.

Die folgende Abbildung zeigt eine Klasse WorkItem , die ein Arbeitselement in einem bestimmten Geschäftsprozess darstellt. Wie alle Klassen wird sie von System.Object abgeleitet und erbt alle dessen Methoden. WorkItem fügt sechs eigene Mitglieder hinzu. Diese Mitglieder umfassen einen Konstruktor, weil Konstruktoren nicht geerbt werden. Die Klasse ChangeRequest erbt von WorkItem und stellt eine bestimmte Art von Arbeitsaufgabe dar. ChangeRequest fügt den von WorkItem und Object geerbten Membern zwei weitere Member hinzu. Es muss seinen eigenen Konstruktor hinzufügen, und es fügt auch originalItemID hinzu. Mit der Eigenschaft originalItemID kann die ChangeRequest Instanz dem Original WorkItem zugeordnet werden, für das die Änderungsanforderung gilt.

Diagramm, das die Klassenvererbung zeigt

Das folgende Beispiel zeigt, wie die in der vorherigen Abbildung gezeigten Klassenbeziehungen in C# ausgedrückt werden. Das Beispiel zeigt auch, wie WorkItem die virtuelle Methode Object.ToString außer Kraft setzt, und wie die ChangeRequest-Klasse die WorkItem-Implementierung der Methode erbt. Der erste Block definiert die Klassen:

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

Dieser nächste Block zeigt, wie die Basis- und abgeleiteten Klassen verwendet werden:

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

Abstrakte und virtuelle Methoden

Wenn eine Basisklasse eine Methode als virtual deklariert, kann eine abgeleitete Klasse override die Methode mit einer eigenen Implementierung überschreiben. Wenn eine Basisklasse ein Element als abstractdeklariert, muss diese Methode in einer nicht abstrakten Klasse überschrieben werden, die direkt von dieser Klasse erbt. Wenn eine abgeleitete Klasse selbst abstrakt ist, erbt sie abstrakte Member, ohne sie zu implementieren. Abstrakte und virtuelle Member sind die Basis für Polymorphismus, was das zweite Hauptmerkmal der objektorientierten Programmierung ist. Weitere Informationen finden Sie unter Polymorphismus.

Abstrakte Basisklassen

Sie können eine Klasse als abstrakt deklarieren, wenn Sie die direkte Instanziierung mithilfe des neuen Operators verhindern möchten. Eine abstrakte Klasse kann nur verwendet werden, wenn eine neue Klasse daraus abgeleitet wird. Eine abstrakte Klasse kann eine oder mehrere Methodensignaturen enthalten, die selbst als abstrakt deklariert werden. Diese Signaturen geben die Parameter und den Rückgabewert an, weisen jedoch keine Implementierung (Methodentext) auf. Eine abstrakte Klasse muss keine abstrakten Member enthalten; Wenn eine Klasse jedoch ein abstraktes Element enthält, muss die Klasse selbst als abstrakt deklariert werden. Abgeleitete Klassen, die nicht abstrahieren, müssen die Implementierung für abstrakte Methoden aus einer abstrakten Basisklasse bereitstellen.

Schnittstellen

Eine Schnittstelle ist ein Verweistyp, der eine Gruppe von Elementen definiert. Alle Klassen und Strukturen, die diese Schnittstelle implementieren, müssen diesen Satz von Membern implementieren. Eine Schnittstelle kann eine Standardimplementierung für einige oder alle dieser Member definieren. Eine Klasse kann mehrere Schnittstellen implementieren, obwohl sie nur von einer einzigen direkten Basisklasse abgeleitet werden kann.

Schnittstellen werden verwendet, um bestimmte Funktionen für Klassen zu definieren, die nicht notwendigerweise über eine "is a"-Beziehung verfügen. Beispielsweise kann die System.IEquatable<T> Schnittstelle von einer beliebigen Klasse oder Struktur implementiert werden, um zu bestimmen, ob zwei Objekte des Typs gleichwertig sind (der Typ definiert jedoch die Äquivalenz). IEquatable<T> bedeutet nicht dieselbe Art von "ist eine" Beziehung, die zwischen einer Basisklasse und einer abgeleiteten Klasse vorhanden ist (z. B. eine Mammal ist ein Animal). Weitere Informationen finden Sie unter Schnittstellen.

Verhindern der weiteren Ableitung

Eine Klasse kann verhindern, dass andere Klassen davon oder von einem ihrer Member erben, indem sie sich selbst oder das Element als sealeddeklarieren.

Verbergen von Basisklassenmembern durch abgeleitete Klassen

Eine abgeleitete Klasse kann Mitglieder der Basisklasse verbergen, indem sie Mitglieder mit demselben Namen und derselben Signatur definiert. Der new Modifizierer kann verwendet werden, um explizit anzugeben, dass das Element keine Außerkraftsetzung des Basiselements sein soll. Die Verwendung von new ist nicht erforderlich, aber es wird eine Compilerwarnung generiert, wenn new nicht verwendet wird. Weitere Informationen finden Sie unter Versionsverwaltung mit den Schlüsselwörtern Override und New und wann die Schlüsselwörter Override und New verwendet werden sollten.