Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Lernprogramm wird die Vererbung in C# vorgestellt. Die Vererbung ist ein Feature objektorientierter Programmiersprachen, mit der Sie eine Basisklasse definieren können, die bestimmte Funktionen (Daten und Verhalten) bereitstellt, und abgeleitete Klassen definieren, die diese Funktionalität erben oder überschreiben.
Voraussetzungen
- Das neueste .NET SDK
- Visual Studio Code-Editor
- Das C# DevKit
Installationsanweisungen
Unter Windows diese WinGet-Konfigurationsdatei zum Installieren aller erforderlichen Komponenten. Wenn Sie bereits etwas installiert haben, überspringt WinGet diesen Schritt.
- Laden Sie die Datei herunter, und doppelklicken Sie, um sie auszuführen.
- Lesen Sie den Lizenzvertrag, geben Sie yein, und wählen Sie Geben Sie ein, wenn Sie zur Annahme aufgefordert werden.
- Wenn Sie in Ihrer Taskleiste eine blinkende Eingabeaufforderung für die Benutzerkontensteuerung (User Account Control, UAC) erhalten, können Sie die Installation fortsetzen.
Auf anderen Plattformen müssen Sie jede dieser Komponenten separat installieren.
- Laden Sie das empfohlene Installationsprogramm von der .NET SDK-Downloadseite herunter , und doppelklicken Sie, um es auszuführen. Die Downloadseite erkennt Ihre Plattform und empfiehlt das neueste Installationsprogramm für Ihre Plattform.
- Laden Sie das neueste Installationsprogramm von der Visual Studio Code Startseite herunter, und doppelklicken Sie, um es auszuführen. Diese Seite erkennt auch Ihre Plattform, und der Link sollte für Ihr System korrekt sein.
- Klicken Sie auf der Erweiterungsseite C# DevKit auf die Schaltfläche "Installieren". Dadurch wird Visual Studio-Code geöffnet und gefragt, ob Sie die Erweiterung installieren oder aktivieren möchten. Wählen Sie "Installieren" aus.
Ausführen der Beispiele
Um die Beispiele in diesem Lernprogramm zu erstellen und auszuführen, verwenden Sie das Dotnet-Hilfsprogramm über die Befehlszeile. Führen Sie die folgenden Schritte für jedes Beispiel aus:
Erstellen Sie ein Verzeichnis zum Speichern des Beispiels.
Geben Sie den neuen Konsolenbefehl dotnet an einer Eingabeaufforderung ein, um ein neues .NET Core-Projekt zu erstellen.
Kopieren Sie den Code aus dem Beispiel, und fügen Sie ihn in Den Code-Editor ein.
Geben Sie den Befehl "dotnet restore " über die Befehlszeile ein, um die Abhängigkeiten des Projekts zu laden oder wiederherzustellen.
Sie müssen
dotnet restore
nicht ausführen, da der Befehl implizit von allen Befehlen ausgeführt wird, die eine Wiederherstellung erfordern. Zu diesen zählen z. B.dotnet new
,dotnet build
,dotnet run
,dotnet test
,dotnet publish
unddotnet pack
. Verwenden Sie die Option--no-restore
, um die implizite Wiederherstellung zu deaktivieren.In bestimmten Fällen eignet sich der
dotnet restore
-Befehl dennoch. Dies ist etwa bei Szenarios der Fall, in denen die explizite Wiederherstellung sinnvoll ist. Beispiele hierfür sind Continuous Integration-Builds in Azure DevOps Services oder Buildsysteme, die den Zeitpunkt für die Wiederherstellung explizit steuern müssen.Informationen zum Verwalten von NuGet-Feeds finden Sie in der
dotnet restore
Dokumentation.Geben Sie den Befehl "dotnet run " ein, um das Beispiel zu kompilieren und auszuführen.
Hintergrund: Was ist Vererbung?
Die Vererbung ist eines der grundlegenden Attribute der objektorientierten Programmierung. Sie können damit eine untergeordnete Klasse definieren, die das Verhalten einer übergeordneten Klasse wiederverwendet (erbt), erweitert oder ändert. Die Klasse, deren Mitglieder geerbt werden, wird als die sogenannte Basisklassebezeichnet. Die Klasse, die die Mitglieder der Basisklasse erbt, wird als abgeleitete Klassebezeichnet.
C# und .NET unterstützen nur die einzelne Vererbung . Das heißt, eine Klasse kann nur von einer einzigen Klasse erben. Die Vererbung ist jedoch transitiv, sodass Sie eine Vererbungshierarchie für einen Satz von Typen definieren können. Mit anderen Worten: Der Typ D
kann vom Typ C
erben, der vom Typ B
erbt, der vom Basisklassentyp A
erbt. Da die Vererbung transitiv ist, sind die Mitglieder des Typs A
für den Typ D
verfügbar.
Nicht alle Mitglieder einer Basisklasse werden von abgeleiteten Klassen vererbt. Die folgenden Mitglieder werden nicht vererbt.
Statische Konstruktoren, die die statischen Daten einer Klasse initialisieren.
Instanzkonstruktoren, die Sie aufrufen, um eine neue Instanz der Klasse zu erstellen. Jede Klasse muss eigene Konstruktoren definieren.
Finalizer, die vom Garbage Collector der Laufzeit aufgerufen werden, um Instanzen einer Klasse zu zerstören.
Während alle anderen Member einer Basisklasse von abgeleiteten Klassen geerbt werden, hängt ihre Sichtbarkeit davon ab, ob auf sie zugegriffen werden kann. Ob auf einen Member zugegriffen werden kann, beeinflusst dessen Sichtbarkeit für abgeleitete Klassen wie folgt:
Private Member sind nur in abgeleiteten Klassen sichtbar, die in ihrer Basisklasse geschachtelt sind. Andernfalls sind sie in abgeleiteten Klassen nicht sichtbar. Im folgenden Beispiel handelt es sich um eine geschachtelte Klasse,
A.B
, die vonA
abgeleitet wird, undC
, das vonA
abgeleitet wird. Das privateA._value
Feld ist in A.B sichtbar. Wenn Sie jedoch die Kommentare aus derC.GetValue
Methode entfernen und versuchen, das Beispiel zu kompilieren, erzeugt es Compilerfehler CS0122: "'A._value' ist aufgrund seiner Schutzebene nicht zugänglich."public class A { private int _value = 10; public class B : A { public int GetValue() { return _value; } } } public class C : A { // public int GetValue() // { // return _value; // } } public class AccessExample { public static void Main(string[] args) { var b = new A.B(); Console.WriteLine(b.GetValue()); } } // The example displays the following output: // 10
Geschützte Member sind nur in abgeleiteten Klassen sichtbar.
Interne Member sind nur in abgeleiteten Klassen sichtbar, die sich in derselben Assembly wie die Basisklasse befinden. Sie sind in abgeleiteten Klassen, die sich in einer anderen Assembly befinden, nicht sichtbar als die Basisklasse.
Öffentliche Member sind in abgeleiteten Klassen sichtbar und Teil der öffentlichen Schnittstelle der abgeleiteten Klasse. Öffentlich geerbte Member können so aufgerufen werden, als ob sie in der abgeleiteten Klasse definiert sind. Im folgenden Beispiel definiert die Klasse
A
eine Methode mit dem NamenMethod1
und die KlasseB
erbt von der KlasseA
. Das Beispiel ruft dann so aufMethod1
, als wäre es eine Instanzmethode fürB
.public class A { public void Method1() { // Method implementation. } } public class B : A { } public class Example { public static void Main() { B b = new (); b.Method1(); } }
Abgeleitete Klassen können auch geerbte Member überschreiben, indem sie eine alternative Implementierung bereitstellen. Um ein Element außer Kraft setzen zu können, muss das Element in der Basisklasse mit dem virtuellen Schlüsselwort gekennzeichnet werden. Standardmäßig sind Member der Basisklasse nicht als virtual
markiert und können nicht überschrieben werden. Der Versuch, wie im folgenden Beispiel gezeigt einen nicht virtuellen Member zu überschreiben, verursacht den Compilerfehler CS0506: "<Member>: Der geerbte Member <Member> kann nicht überschrieben werden, da er nicht als „virtual“, „abstract“ oder „override“ markiert ist."
public class A
{
public void Method1()
{
// Do something.
}
}
public class B : A
{
public override void Method1() // Generates CS0506.
{
// Do something else.
}
}
In einigen Fällen muss eine abgeleitete Klasse die Implementierung der Basisklasse überschreiben. Basisklassenmitglieder, die mit dem abstract Schlüsselwort gekennzeichnet sind, erfordern, dass abgeleitete Klassen sie überschreiben. Beim Kompilieren des folgenden Beispiels wird der Compilerfehler CS0534 generiert: "<Klasse> implementiert das geerbte abstrakte Mitglied <member>" nicht, da die Klasse B
keine Implementierung für A.Method1
bietet.
public abstract class A
{
public abstract void Method1();
}
public class B : A // Generates CS0534.
{
public void Method3()
{
// Do something.
}
}
Die Vererbung gilt nur für Klassen und Schnittstellen. Andere Typkategorien (Strukturen, Delegate und Enumerationen) unterstützen keine Vererbung. Aufgrund dieser Regeln erzeugt der Versuch, Code wie das folgende Beispiel zu kompilieren, den Compilerfehler CS0527: "Typ 'ValueType' in der Schnittstellenliste ist keine Schnittstelle." Die Fehlermeldung gibt an, dass die Vererbung nicht unterstützt wird, obwohl Sie die Schnittstellen definieren können, die eine Struktur implementiert.
public struct ValueStructure : ValueType // Generates CS0527.
{
}
Implizite Vererbung
Neben den Typen, die sie durch Einzelvererbung erben können, erben alle Typen im .NET-Typsystem implizit von Object oder einem davon abgeleiteten Typ. Die allgemeine Funktionalität von Object steht allen Typen zur Verfügung.
Um zu sehen, was implizite Vererbung bedeutet, definieren wir eine neue Klasse, SimpleClass
d. h. einfach eine leere Klassendefinition:
public class SimpleClass
{ }
Anschließend können Sie die Spiegelung verwenden (mit der Sie die Metadaten eines Typs überprüfen können, um Informationen zu diesem Typ abzurufen), um eine Liste der Mitglieder abzurufen, die zum SimpleClass
Typ gehören. Obwohl Sie keine Member in Ihrer SimpleClass
Klasse definiert haben, gibt die Ausgabe aus dem Beispiel an, dass sie tatsächlich neun Elemente enthält. Eines dieser Member ist ein parameterloser (oder Standard)-Konstruktor, der automatisch vom C#-Compiler für den SimpleClass
Typ bereitgestellt wird. Die verbleibenden acht sind Member von Object, dem Typ, von dem alle Klassen und Schnittstellen im .NET-Typsystem letztlich implizit erben.
using System.Reflection;
public class SimpleClassExample
{
public static void Main()
{
Type t = typeof(SimpleClass);
BindingFlags flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
MemberInfo[] members = t.GetMembers(flags);
Console.WriteLine($"Type {t.Name} has {members.Length} members: ");
foreach (MemberInfo member in members)
{
string access = "";
string stat = "";
var method = member as MethodBase;
if (method != null)
{
if (method.IsPublic)
access = " Public";
else if (method.IsPrivate)
access = " Private";
else if (method.IsFamily)
access = " Protected";
else if (method.IsAssembly)
access = " Internal";
else if (method.IsFamilyOrAssembly)
access = " Protected Internal ";
if (method.IsStatic)
stat = " Static";
}
string output = $"{member.Name} ({member.MemberType}): {access}{stat}, Declared by {member.DeclaringType}";
Console.WriteLine(output);
}
}
}
// The example displays the following output:
// Type SimpleClass has 9 members:
// ToString (Method): Public, Declared by System.Object
// Equals (Method): Public, Declared by System.Object
// Equals (Method): Public Static, Declared by System.Object
// ReferenceEquals (Method): Public Static, Declared by System.Object
// GetHashCode (Method): Public, Declared by System.Object
// GetType (Method): Public, Declared by System.Object
// Finalize (Method): Internal, Declared by System.Object
// MemberwiseClone (Method): Internal, Declared by System.Object
// .ctor (Constructor): Public, Declared by SimpleClass
Die implizite Vererbung von der Object Klasse macht folgende Methoden für die SimpleClass
Klasse verfügbar:
Die öffentliche
ToString
Methode, die einSimpleClass
Objekt in seine Zeichenfolgendarstellung konvertiert, gibt den vollqualifizierten Typnamen zurück. In diesem Fall gibt dieToString
Methode die Zeichenfolge "SimpleClass" zurück.Drei Methoden, die die Gleichheit von zwei Objekten testen: die öffentliche Instanzmethode
Equals(Object)
, die öffentliche statischeEquals(Object, Object)
Methode und die öffentliche statischeReferenceEquals(Object, Object)
Methode. Standardmäßig testen diese Methoden die Referenzgleichheit; d. h. zwei Objektvariablen müssen auf dasselbe Objekt verweisen.Die öffentliche
GetHashCode
Methode, die einen Wert berechnet, mit dem eine Instanz des Typs in Hashsammlungen verwendet werden kann.Die öffentliche
GetType
Methode, die ein Type Objekt zurückgibt, das denSimpleClass
Typ darstellt.Die geschützte Finalize Methode, die dazu dient, nicht verwaltete Ressourcen freizugeben, bevor der Speicher eines Objekts durch den Garbage Collector zurückgewonnen wird.
Die geschützte MemberwiseClone Methode, die einen flachen Klon des aktuellen Objekts erstellt.
Aufgrund der impliziten Vererbung können Sie jedes geerbte Element von einem SimpleClass
Objekt so aufrufen, als wäre es tatsächlich ein Element, das in der SimpleClass
Klasse definiert wurde. Im folgenden Beispiel wird die Methode SimpleClass.ToString
aufgerufen, die von SimpleClass
Object geerbt wird.
public class EmptyClass
{ }
public class ClassNameExample
{
public static void Main()
{
EmptyClass sc = new();
Console.WriteLine(sc.ToString());
}
}
// The example displays the following output:
// EmptyClass
In der folgenden Tabelle sind die Kategorien von Typen aufgeführt, die Sie in C# erstellen können, und die Typen, von denen sie implizit erben. Jeder Basistyp macht implizit abgeleiteten Typen über Vererbung einen anderen Satz von Membern verfügbar.
Typkategorie | Erbt implizit von |
---|---|
Klasse | Object |
Struktur | ValueType, Object |
Enumeration | Enum, ValueTypeObject |
delegieren | MulticastDelegate, DelegateObject |
Vererbung und eine „ist ein“-Beziehung
Normalerweise wird Vererbung verwendet, um eine "ist-eine"-Beziehung zwischen einer Basisklasse und einer oder mehreren abgeleiteten Klassen auszudrücken. Dabei sind die abgeleiteten Klassen spezialisierte Versionen der Basisklasse; die abgeleitete Klasse ist ein Typ der Basisklasse. Beispielsweise stellt die Klasse Publication
eine Publikation beliebiger Art dar, und die Klassen Book
und Magazine
stellen bestimmte Arten von Publikationen dar.
Hinweis
Eine Klasse oder Struktur kann eine oder mehrere Schnittstellen implementieren. Die Schnittstellenimplementierung wird zwar oft als Problemumgehung für einzelne Vererbung oder Möglichkeit der Verwendung von Vererbung mit Strukturen dargestellt, doch sie soll eine andere Beziehung (eine „tun können“-Beziehung) zwischen einer Schnittstelle und ihrem implementierenden Typ ausdrücken als Vererbung. Eine Schnittstelle definiert eine Teilmenge von Funktionen (z. B. die Möglichkeit zum Testen der Gleichheit, zum Vergleichen oder Sortieren von Objekten oder zur Unterstützung von kultursensitiver Analyse und Formatierung), die die Schnittstelle für die Implementierungstypen zur Verfügung stellt.
Beachten Sie, dass "is a" auch die Beziehung zwischen einem Typ und einer bestimmten Instanziierung dieses Typs ausdrückt. Im folgenden Beispiel ist Automobile
eine Klasse mit drei einzigartigen, schreibgeschützten Eigenschaften: Make
, der Hersteller des Automobils; Model
, die Art des Automobils; und Year
, das Jahr der Herstellung. Ihre Automobile
Klasse verfügt auch über einen Konstruktor, dessen Argumente den Eigenschaftswerten zugewiesen sind, und überschreibt die Object.ToString Methode, um eine Zeichenfolge zu erzeugen, die die Automobile
Instanz eindeutig identifiziert, anstatt die Automobile
Klasse.
public class Automobile
{
public Automobile(string make, string model, int year)
{
if (make == null)
throw new ArgumentNullException(nameof(make), "The make cannot be null.");
else if (string.IsNullOrWhiteSpace(make))
throw new ArgumentException("make cannot be an empty string or have space characters only.");
Make = make;
if (model == null)
throw new ArgumentNullException(nameof(model), "The model cannot be null.");
else if (string.IsNullOrWhiteSpace(model))
throw new ArgumentException("model cannot be an empty string or have space characters only.");
Model = model;
if (year < 1857 || year > DateTime.Now.Year + 2)
throw new ArgumentException("The year is out of range.");
Year = year;
}
public string Make { get; }
public string Model { get; }
public int Year { get; }
public override string ToString() => $"{Year} {Make} {Model}";
}
In diesem Fall sollten Sie sich nicht auf die Vererbung verlassen, um bestimmte Autohersteller und -modelle darzustellen. Sie müssen beispielsweise keinen Packard
Typ definieren, der Automobile repräsentiert, die von der Packard Motor Car Company hergestellt werden. Stattdessen können Sie sie darstellen, indem Sie wie im folgenden Beispiel ein Automobile
Objekt mit den entsprechenden Werten erstellen, die an den Klassenkonstruktor übergeben werden.
using System;
public class Example
{
public static void Main()
{
var packard = new Automobile("Packard", "Custom Eight", 1948);
Console.WriteLine(packard);
}
}
// The example displays the following output:
// 1948 Packard Custom Eight
Eine auf Vererbung basierende Beziehung wird am besten auf eine Basisklasse und auf abgeleitete Klassen angewendet, die der Basisklasse zusätzliche Elemente hinzufügen oder zusätzliche Funktionen erfordern, die in der Basisklasse nicht vorhanden sind.
Entwerfen der Basisklasse und abgeleiteter Klassen
Sehen wir uns den Prozess des Entwerfens einer Basisklasse und der abgeleiteten Klassen an. In diesem Abschnitt definieren Sie eine Basisklasse, Publication
, die eine Publikation jeglicher Art darstellt, z. B. ein Buch, eine Zeitschrift, eine Zeitung, ein Journal, einen Artikel usw. Darüber hinaus definieren Sie eine Klasse Book
, die von Publication
abgeleitet ist. Sie können das Beispiel ganz einfach erweitern, um andere abgeleitete Klassen zu definieren, wie z. B. Magazine
, Journal
, Newspaper
und Article
.
Die Basisklasse „Publication“
Beim Entwerfen Ihrer Publication
Klasse müssen Sie mehrere Designentscheidungen treffen:
Welche Member in Ihre Basisklasse
Publication
einbezogen werden sollen und ob diePublication
Member Methodenimplementierungen bereitstellen oder ob esPublication
sich um eine abstrakte Basisklasse handelt, die als Vorlage für die abgeleiteten Klassen dient.In diesem Fall stellt die
Publication
Klasse Methodenimplementierungen bereit. Der Abschnitt "Entwerfen abstrakter Basisklassen" und deren abgeleiteter Klassen enthält ein Beispiel, das eine abstrakte Basisklasse verwendet, um die Methoden zu definieren, die abgeleitete Klassen überschreiben müssen. Abgeleitete Klassen können jede Implementierung bereitstellen, die für den abgeleiteten Typ geeignet ist.Die Möglichkeit, Code wiederzuverwenden (d. h. mehrere abgeleitete Klassen teilen die Deklaration und Implementierung von Basisklassenmethoden und müssen sie nicht überschreiben), ist ein Vorteil nicht abstrakter Basisklassen. Daher sollten Sie Mitglieder zu
Publication
hinzufügen, wenn ihr Code wahrscheinlich von einigen oder den meisten spezialisiertenPublication
Typen geteilt wird. Wenn Sie Basisklassenimplementierungen nicht effizient bereitstellen, müssen Sie letztendlich weitgehend identische Memberimplementierungen in abgeleiteten Klassen bereitstellen, anstatt eine einzelne Implementierung in der Basisklasse. Die Notwendigkeit, doppelten Code an mehreren Speicherorten beizubehalten, ist eine potenzielle Quelle von Fehlern.Um die Wiederverwendung von Code zu maximieren und eine logische und intuitive Vererbungshierarchie zu erstellen, sollten Sie sicherstellen, dass Sie nur die Daten und Funktionen, die für alle oder für die meisten Publikationen gelten, in die
Publication
Klasse aufnehmen. Abgeleitete Klassen implementieren dann Elemente, die für die jeweiligen Publikationsarten eindeutig sind, die sie darstellen.Wie weit Ihre Klassenhierarchie erweitert werden soll. Möchten Sie eine Hierarchie von drei oder mehr Klassen entwickeln, anstatt einfach nur eine Basisklasse und eine oder mehrere abgeleitete Klassen? Beispielsweise könnte
Publication
eine Basisklasse vonPeriodical
sein, die wiederum eine Basisklasse vonMagazine
,Journal
undNewspaper
ist.Beispielsweise verwenden Sie die kleine Hierarchie einer
Publication
Klasse und eine einzelne abgeleitete Klasse.Book
Sie können das Beispiel ganz einfach erweitern, um mehrere zusätzliche Klassen zu erstellen, die vonPublication
abgeleitet sind, wieMagazine
undArticle
.Ob es sinnvoll ist, die Basisklasse zu instanziieren. Wenn dies nicht der Fall ist, sollten Sie das Schlüsselwort abstract auf die Klasse anwenden. Andernfalls kann Ihre
Publication
Klasse durch Aufrufen des Klassenkonstruktors instanziiert werden. Wenn versucht wird, eine Klasse zu instanziieren, die durch einen direkten Aufruf des Klassenkonstruktors mit demabstract
Schlüsselwort gekennzeichnet ist, generiert der C#-Compiler fehler CS0144, "Eine Instanz der abstrakten Klasse oder Schnittstelle kann nicht erstellt werden." Wenn versucht wird, die Klasse mithilfe der Spiegelung instanziieren zu können, löst die Spiegelungsmethode eine MemberAccessException.Standardmäßig kann eine Basisklasse durch Aufrufen des Klassenkonstruktors instanziiert werden. Sie müssen keinen Klassenkonstruktor explizit definieren. Wenn eine nicht im Quellcode der Basisklasse vorhanden ist, stellt der C#-Compiler automatisch einen Standardkonstruktor (parameterlose) bereit.
Beispielsweise markieren Sie die
Publication
Klasse als abstrakt , damit sie nicht instanziiert werden kann. Eineabstract
Klasse ohneabstract
Methoden gibt an, dass diese Klasse ein abstraktes Konzept darstellt, das von mehreren konkreten Klassen (z. B. einemBook
, )Journal
gemeinsam genutzt wird.Gibt an, ob abgeleitete Klassen die Basisklassenimplementierung bestimmter Member erben müssen, unabhängig davon, ob sie die Basisklassenimplementierung überschreiben können oder ob sie eine Implementierung bereitstellen müssen. Sie verwenden das abstract-Schlüsselwort, damit abgeleitete Klassen eine Implementierung bereitstellen. Sie verwenden das virtuelle Schlüsselwort, um abgeleiteten Klassen das Überschreiben einer Basisklassenmethode zu ermöglichen. Standardmäßig sind in der Basisklasse definierte Methoden nicht überschreibbar.
Die
Publication
Klasse hatabstract
keine Methoden, aber die Klasse selbst istabstract
.Gibt an, ob eine abgeleitete Klasse die endgültige Klasse in der Vererbungshierarchie darstellt und nicht selbst als Basisklasse für zusätzliche abgeleitete Klassen verwendet werden kann. Standardmäßig kann jede Klasse als Basisklasse dienen. Sie können das versiegelte Schlüsselwort anwenden, um anzugeben, dass eine Klasse nicht als Basisklasse für weitere Klassen dienen kann. Der Versuch, von einer versiegelten Klasse abzuleiten, generierte Compilerfehler CS0509, "kann nicht von versiegeltem Typ <typeName> abgeleitet werden."
In Ihrem Beispiel markieren Sie ihre abgeleitete Klasse als
sealed
.
Das folgende Beispiel zeigt den Quellcode für die Publication
Klasse sowie eine PublicationType
Aufzählung, die von der Publication.PublicationType
Eigenschaft zurückgegeben wird. Zusätzlich zu den Membern, die sie von Object erbt, definiert die Publication
-Klasse die folgenden eindeutigen Member und Memberüberschreibungen:
public enum PublicationType { Misc, Book, Magazine, Article };
public abstract class Publication
{
private bool _published = false;
private DateTime _datePublished;
private int _totalPages;
public Publication(string title, string publisher, PublicationType type)
{
if (string.IsNullOrWhiteSpace(publisher))
throw new ArgumentException("The publisher is required.");
Publisher = publisher;
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentException("The title is required.");
Title = title;
Type = type;
}
public string Publisher { get; }
public string Title { get; }
public PublicationType Type { get; }
public string? CopyrightName { get; private set; }
public int CopyrightDate { get; private set; }
public int Pages
{
get { return _totalPages; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), "The number of pages cannot be zero or negative.");
_totalPages = value;
}
}
public string GetPublicationDate()
{
if (!_published)
return "NYP";
else
return _datePublished.ToString("d");
}
public void Publish(DateTime datePublished)
{
_published = true;
_datePublished = datePublished;
}
public void Copyright(string copyrightName, int copyrightDate)
{
if (string.IsNullOrWhiteSpace(copyrightName))
throw new ArgumentException("The name of the copyright holder is required.");
CopyrightName = copyrightName;
int currentYear = DateTime.Now.Year;
if (copyrightDate < currentYear - 10 || copyrightDate > currentYear + 2)
throw new ArgumentOutOfRangeException($"The copyright year must be between {currentYear - 10} and {currentYear + 1}");
CopyrightDate = copyrightDate;
}
public override string ToString() => Title;
}
Ein Konstruktor
Da die
Publication
Klasse lautetabstract
, kann sie nicht direkt aus Code instanziiert werden, wie im folgenden Beispiel:var publication = new Publication("Tiddlywinks for Experts", "Fun and Games", PublicationType.Book);
Der Instanzkonstruktor kann jedoch direkt von abgeleiteten Klassenkonstruktoren aufgerufen werden, wie der Quellcode für die
Book
Klasse zeigt.Zwei publikationsbezogene Eigenschaften
Title
ist eine schreibgeschützte String-Eigenschaft, deren Wert durch Aufrufen desPublication
-Konstruktors bereitgestellt wird.Pages
ist eine Lese-/Schreibeigenschaft Int32 , die angibt, wie viele Seiten die Publikation hat. Der Wert wird in einem privaten Feld mit dem NamentotalPages
gespeichert. Es muss eine positive Zahl sein, sonst wird eine ArgumentOutOfRangeException ausgelöst.Publisher-bezogene Mitglieder
Zwei schreibgeschützte Eigenschaften,
Publisher
undType
. Die Werte werden ursprünglich vom Aufruf desPublication
Klassenkonstruktors bereitgestellt.Veröffentlichungsbezogene Elemente
Zwei Methoden
Publish
undGetPublicationDate
, festlegen und zurückgeben das Publikationsdatum. DiePublish
-Methode setzt beim Aufrufen das privatepublished
-Flag auftrue
und weist das als Argument übergebene Datum dem privatendatePublished
-Feld zu. DieGetPublicationDate
Methode gibt die Zeichenfolge "NYP" zurück, wenn daspublished
Flag auffalse
gesetzt ist, und den Wert desdatePublished
Felds, wenn diesertrue
ist.Copyrightbezogene Elemente
Die
Copyright
Methode nimmt den Namen des Copyrightinhabers und das Jahr des Urheberrechts als Argumente und weist sie an die EigenschaftenCopyrightName
undCopyrightDate
.Eine Außerkraftsetzung der
ToString
MethodeWenn ein Typ die Object.ToString Methode nicht überschreibt, gibt er den vollqualifizierten Namen des Typs zurück, der bei der Unterscheidung einer Instanz von einer anderen nur wenig verwendet wird. Die
Publication
-Klasse überschreibt Object.ToString, um den Wert derTitle
-Eigenschaft zurückzugeben.
Die folgende Abbildung veranschaulicht die Beziehung zwischen Ihrer Basisklasse Publication
und der implizit geerbten Klasse Object.
Die Book
-Klasse
Die Book
Klasse stellt ein Buch als spezialisierten Publikationstyp dar. Das folgende Beispiel zeigt den Quellcode für die Book
Klasse.
using System;
public sealed class Book : Publication
{
public Book(string title, string author, string publisher) :
this(title, string.Empty, author, publisher)
{ }
public Book(string title, string isbn, string author, string publisher) : base(title, publisher, PublicationType.Book)
{
// isbn argument must be a 10- or 13-character numeric string without "-" characters.
// We could also determine whether the ISBN is valid by comparing its checksum digit
// with a computed checksum.
//
if (!string.IsNullOrEmpty(isbn))
{
// Determine if ISBN length is correct.
if (!(isbn.Length == 10 | isbn.Length == 13))
throw new ArgumentException("The ISBN must be a 10- or 13-character numeric string.");
if (!ulong.TryParse(isbn, out _))
throw new ArgumentException("The ISBN can consist of numeric characters only.");
}
ISBN = isbn;
Author = author;
}
public string ISBN { get; }
public string Author { get; }
public decimal Price { get; private set; }
// A three-digit ISO currency symbol.
public string? Currency { get; private set; }
// Returns the old price, and sets a new price.
public decimal SetPrice(decimal price, string currency)
{
if (price < 0)
throw new ArgumentOutOfRangeException(nameof(price), "The price cannot be negative.");
decimal oldValue = Price;
Price = price;
if (currency.Length != 3)
throw new ArgumentException("The ISO currency symbol is a 3-character string.");
Currency = currency;
return oldValue;
}
public override bool Equals(object? obj)
{
if (obj is not Book book)
return false;
else
return ISBN == book.ISBN;
}
public override int GetHashCode() => ISBN.GetHashCode();
public override string ToString() => $"{(string.IsNullOrEmpty(Author) ? "" : Author + ", ")}{Title}";
}
Zusätzlich zu den Membern, die sie von Publication
erbt, definiert die Book
-Klasse die folgenden eindeutigen Member und Memberüberschreibungen:
Zwei Konstruktoren
Die beiden
Book
Konstruktoren verwenden drei gemeinsame Parameter. Zwei, Titel und Herausgeber entsprechen Parametern desPublication
Konstruktors. Der dritte ist author, der in einer öffentlichen unveränderlichenAuthor
-Eigenschaft gespeichert ist. Ein Konstruktor enthält einen ISBN-Parameter , der in derISBN
auto-Eigenschaft gespeichert ist.Der erste Konstruktor verwendet dieses Schlüsselwort, um den anderen Konstruktor aufzurufen. Die Konstruktorverkettung ist ein häufiges Muster beim Definieren von Konstruktoren. Konstruktoren mit weniger Parametern bieten Standardwerte beim Aufrufen des Konstruktors mit der größten Anzahl von Parametern.
Der zweite Konstruktor verwendet das Basisschlüsselwort , um den Titel und den Herausgebernamen an den Basisklassenkonstruktor zu übergeben. Wenn Sie keinen expliziten Aufruf eines Basisklassenkonstruktors in Ihrem Quellcode ausführen, stellt der C#-Compiler automatisch einen Aufruf des Standard- oder Parameterlosen Konstruktors der Basisklasse bereit.
Eine schreibgeschützte Eigenschaft
ISBN
, die die ISBN desBook
-Objekts zurückgibt, eine eindeutige 10- oder 13-stellige Nummer. Die ISBN wird als Argument für einen derBook
Konstruktoren bereitgestellt. Die ISBN wird in einem privaten Sicherungsfeld gespeichert, das automatisch vom Compiler generiert wird.Eine schreibgeschützte Eigenschaft
Author
. Der Name des Autors wird als Argument für beideBook
Konstruktoren angegeben und in der Eigenschaft gespeichert.Zwei schreibgeschützte preisbezogene Eigenschaften,
Price
undCurrency
. Ihre Werte werden als Argumente in einemSetPrice
Methodenaufruf bereitgestellt. DieCurrency
Eigenschaft ist das dreistellige ISO-Währungssymbol (z. B. USD für den US-Dollar). ISO-Währungssymbole können aus der ISOCurrencySymbol Eigenschaft abgerufen werden. Beide Eigenschaften sind extern schreibgeschützt, aber beide können durch Code in derBook
Klasse festgelegt werden.Eine
SetPrice
Methode, die die Werte derPrice
UndCurrency
Eigenschaften festlegt. Diese Werte werden durch dieselben Eigenschaften zurückgegeben.Überschreibt die
ToString
-Methode (geerbt vonPublication
) sowie die Object.Equals(Object)- und GetHashCode-Methoden (geerbt von Object).Sofern sie nicht außer Kraft gesetzt wird, prüft die Object.Equals(Object) Methode die Referenzgleichheit. Das heißt, zwei Objektvariablen werden als gleich angesehen, wenn sie auf dasselbe Objekt verweisen. In der
Book
Klasse hingegen sollten zweiBook
Objekte gleich sein, wenn sie die gleiche ISBN haben.Wenn Sie die Object.Equals(Object) Methode überschreiben, müssen Sie auch die GetHashCode Methode überschreiben, die einen Wert zurückgibt, den die Laufzeit zum Speichern von Elementen in Hashauflistungen zum effizienten Abrufen verwendet. Der Hashcode sollte einen Wert zurückgeben, der mit dem Test auf Gleichheit konsistent ist. Da Sie die Methode Object.Equals(Object) überschrieben haben, um
true
zurückzugeben, wenn die ISBN-Eigenschaften von zweiBook
-Objekten gleich sind, geben Sie den von der GetHashCode-Eigenschaft zurückgegebenen Hashcode zurück, indem Sie dieISBN
-Methode der zurückgegebenen Zeichenfolge aufrufen.
In der folgenden Abbildung wird die Beziehung zwischen der Book
Klasse und Publication
der Basisklasse veranschaulicht.
Sie können ein Book
Objekt jetzt instanziieren, sowohl seine eindeutigen als auch geerbten Member aufrufen und als Argument an eine Methode übergeben, die einen Parameter vom Typ Publication
oder typ Book
erwartet, wie im folgenden Beispiel gezeigt.
public class ClassExample
{
public static void Main()
{
var book = new Book("The Tempest", "0971655819", "Shakespeare, William",
"Public Domain Press");
ShowPublicationInfo(book);
book.Publish(new DateTime(2016, 8, 18));
ShowPublicationInfo(book);
var book2 = new Book("The Tempest", "Classic Works Press", "Shakespeare, William");
Console.Write($"{book.Title} and {book2.Title} are the same publication: " +
$"{((Publication)book).Equals(book2)}");
}
public static void ShowPublicationInfo(Publication pub)
{
string pubDate = pub.GetPublicationDate();
Console.WriteLine($"{pub.Title}, " +
$"{(pubDate == "NYP" ? "Not Yet Published" : "published on " + pubDate):d} by {pub.Publisher}");
}
}
// The example displays the following output:
// The Tempest, Not Yet Published by Public Domain Press
// The Tempest, published on 8/18/2016 by Public Domain Press
// The Tempest and The Tempest are the same publication: False
Entwerfen abstrakter Basisklassen und ihrer abgeleiteten Klassen
Im vorherigen Beispiel haben Sie eine Basisklasse definiert, die eine Implementierung für eine Reihe von Methoden bereitgestellt hat, damit abgeleitete Klassen Code freigeben können. In vielen Fällen wird jedoch nicht erwartet, dass die Basisklasse eine Implementierung bereitstellt. Stattdessen handelt es sich bei der Basisklasse um eine abstrakte Klasse , die abstrakte Methoden deklariert. sie dient als Vorlage, die die Member definiert, die jede abgeleitete Klasse implementieren muss. Typischerweise ist in einer abstrakten Basisklasse die Implementierung jedes abgeleiteten Typs einzigartig für diesen Typ. Sie haben die Klasse mit dem abstrakten Schlüsselwort markiert, da es nicht sinnvoll war, ein Publication
Objekt zu instanziieren, obwohl die Klasse Implementierungen von Funktionen bereitstellte, die für Publikationen üblich sind.
Beispielsweise enthält jede geschlossene zweidimensionale geometrische Form zwei Eigenschaften: Fläche, das innere Ausmaß der Form; und Umrandung oder der Abstand entlang der Ränder der Form. Die Art und Weise, in der diese Eigenschaften berechnet werden, hängt jedoch vollständig von der spezifischen Form ab. Die Formel zum Berechnen des Umkreises (oder des Umfangs) eines Kreises unterscheidet sich beispielsweise von der eines Quadrats. Die Shape
Klasse ist eine abstract
Klasse mit abstract
Methoden. Das bedeutet, dass abgeleitete Klassen dieselbe Funktionalität aufweisen, aber diese abgeleiteten Klassen implementieren diese Funktionalität anders.
Im folgenden Beispiel wird eine abstrakte Basisklasse definiert Shape
, die zwei Eigenschaften definiert: Area
und Perimeter
. Neben der Markierung der Klasse mit dem abstract-Schlüsselwort wird jedes Instanzmitglied auch mit dem abstract-Schlüsselwort markiert. In diesem Fall überschreibt Shape
auch die Object.ToString -Methode, um den Namen des Typs anstelle dessen vollqualifizierten Namens zurückzugeben. Außerdem definiert es zwei statische Member, GetArea
und GetPerimeter
, die es Anrufern ermöglichen, den Bereich und den Umkreis einer Instanz einer abgeleiteten Klasse einfach abzurufen. Wenn Sie eine Instanz einer abgeleiteten Klasse an eine dieser Methoden übergeben, ruft die Laufzeit die Methodenüberschreibung der abgeleiteten Klasse auf.
public abstract class Shape
{
public abstract double Area { get; }
public abstract double Perimeter { get; }
public override string ToString() => GetType().Name;
public static double GetArea(Shape shape) => shape.Area;
public static double GetPerimeter(Shape shape) => shape.Perimeter;
}
Anschließend können Sie einige Klassen von Shape
ableiten, die bestimmte Formen repräsentieren. Das folgende Beispiel definiert drei Klassen, Square
, Rectangle
und Circle
. Jede verwendet eine für dieses bestimmte Shape eindeutige Formel, um den Bereich und den Umkreis zu berechnen. Einige der abgeleiteten Klassen definieren auch Eigenschaften, wie Rectangle.Diagonal
und Circle.Diameter
, die einzigartig für die Form sind, die sie darstellen.
using System;
public class Square : Shape
{
public Square(double length)
{
Side = length;
}
public double Side { get; }
public override double Area => Math.Pow(Side, 2);
public override double Perimeter => Side * 4;
public double Diagonal => Math.Round(Math.Sqrt(2) * Side, 2);
}
public class Rectangle : Shape
{
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}
public double Length { get; }
public double Width { get; }
public override double Area => Length * Width;
public override double Perimeter => 2 * Length + 2 * Width;
public bool IsSquare() => Length == Width;
public double Diagonal => Math.Round(Math.Sqrt(Math.Pow(Length, 2) + Math.Pow(Width, 2)), 2);
}
public class Circle : Shape
{
public Circle(double radius)
{
Radius = radius;
}
public override double Area => Math.Round(Math.PI * Math.Pow(Radius, 2), 2);
public override double Perimeter => Math.Round(Math.PI * 2 * Radius, 2);
// Define a circumference, since it's the more familiar term.
public double Circumference => Perimeter;
public double Radius { get; }
public double Diameter => Radius * 2;
}
Im folgenden Beispiel werden Objekte verwendet, die von Shape
abgeleitet sind. Es instanziiert ein Array von Objekten, die von Shape
abgeleitet sind, und ruft die statischen Methoden der Shape
Klasse auf, die Rückgabewerte der Shape
Eigenschaften umschließen. Die Laufzeit ruft Werte aus den überschriebenen Eigenschaften der abgeleiteten Typen ab. Das Beispiel castet auch jedes Shape
Objekt im Array in seinen abgeleiteten Typ und ruft, wenn die Umwandlung erfolgreich ist, die Eigenschaften der entsprechenden Unterklasse von Shape
ab.
using System;
public class Example
{
public static void Main()
{
Shape[] shapes = { new Rectangle(10, 12), new Square(5),
new Circle(3) };
foreach (Shape shape in shapes)
{
Console.WriteLine($"{shape}: area, {Shape.GetArea(shape)}; " +
$"perimeter, {Shape.GetPerimeter(shape)}");
if (shape is Rectangle rect)
{
Console.WriteLine($" Is Square: {rect.IsSquare()}, Diagonal: {rect.Diagonal}");
continue;
}
if (shape is Square sq)
{
Console.WriteLine($" Diagonal: {sq.Diagonal}");
continue;
}
}
}
}
// The example displays the following output:
// Rectangle: area, 120; perimeter, 44
// Is Square: False, Diagonal: 15.62
// Square: area, 25; perimeter, 20
// Diagonal: 7.07
// Circle: area, 28.27; perimeter, 18.85