11. Vererbung Vererbung ist eine der mächtigsten Funktionalitäten der objektorientierten Programmiersprachen. Man versteht unter Vererbung die Tatsache, dass eine Klasse alle Methoden und Variablen einer anderen Klasse ableitet. Eine Klasse muss also nicht immer wieder neu programmiert werden, vielmehr vererbt eine schon fertige Klasse, die einen Großteil der gewünschten Funktionalität aufweist, die Eigenschaften einfach an eine neue Klasse. Hinzu kommt noch die Möglichkeit, dass die Methoden und Variablen in der neuen Klasse nach den eigenen Wünschen erweitert oder abgeändert werden können. Als anschauliches Beispiel könnte man sich ein Fenster unter Windows vorstellen. Sie haben bereits eine Klasse für Fenster geschrieben. Jetzt möchten Sie in einer neuen Klasse ein Fenster mit einer Schließen-Schaltfläche haben. Bevor Sie jetzt alles neu programmieren müssen, erstellen Sie einfach eine neue Klasse, die alle Eigenschaften der alten Fensterklasse erbt, und fügen nur den Code für die Schließen-Schaltfläche ein. Das System der Vererbung hat außer der immensen Zeitersparnis bei der Programmierung den Vorteil, dass Sie sich Klassen schaffen können, die Sie bereits getestet haben. Sie schließen damit sehr viele Fehlerquellen aus. 11.1 Vererbung Beim Vererben bekommt eine neue Klasse alle Methoden und Variablen der Klasse, von der sie erbt. Die Klasse, die diese Eigenschaften vererbt, nennt man Basisklasse oder Superklasse. Als Beispiel beginnen wir mit einer sehr einfachen Klasse: Mensch. Ein Mensch ist in dieser Klasse nur durch den Namen, das Geschlecht und das Alter definiert. Als Methoden gibt es GetName(), GetName(), GetGeschlecht() und AelterWerden(). public class Mensch { private char Geschlecht; // m für männlich, // w für weiblich private string Name; 181
private int Alter; public Mensch(char Geschlecht,string Name,int Alter) { this.geschlecht=geschlecht; this.name=name; this.alter=alter; public void SetName(string Name) { this.name=name; public string GetName() { return Name; public void AelterWerden() { Alter++; public string GetAlter() { return Name; public char GetGeschlecht() { return Geschlecht; Von dieser Klasse leiten wir nun die Klasse Angestellter aus Kapitel 10 ab: public class Angestellter : Mensch { private string Abteilung; public double Gehalt; public Angestellter (char g, string n, int a) : base(g,n,a) { public void SetAbteilung(string a) { Abteilung=a; public string GetAbteilung() { return Abteilung; public void SetGehalt(double g) { if (g<0) Gehalt=g; else Gehalt=g; public double GetGethalt() { return Gehalt; 182
public void ErhoeheGehalt(double g) { Gehalt+=g; public void AusgabeMitarbeiterInfo() { if(getgeschlecht()=='m')console.write("herr "); if(getgeschlecht()=='w')console.write("frau "); Console.WriteLine(GetName()); Console.WriteLine("Gehalt "+Gehalt); Console.WriteLine("Abteilung: "+Abteilung); Interessant dabei sind nur zwei Teile: und public class Angestellter : Mensch { public Angestellter (char g, string n, int a) : base(g,n,a) { Um dem Compiler mitzuteilen, dass er die Klasse Angestellter von der Klasse Mensch ableiten soll, verwenden wir den :-Operator. Jetzt gibt es in der Klasse Angestellter alle Methoden und alle Variablen der Klasse Mensch. Natürlich gelten die Sicherheitsbestimmungen der Klassenmodifizierer. In der Klasse Angestellter kann z. B. nicht direkt auf die Variablen Name und Geschlecht zugegriffen werden. Der zweite wichtige Teil betrifft den Konstruktor. Wenn wir eine Instanz der Klasse Angestellter erstellen, muss natürlich auch der Konstruktor der Klasse Mensch aufgerufen werden. Dazu erbt auch der neue Konstruktor vom Konstruktor der Basisklasse. In unserem Fall geben wir alle an den Konstruktor von Angestellter übergebenen Parameter an den Konstruktor von Mensch weiter. Der Konstruktor der Basisklasse heißt in diesem Fall immer base(). Base() entspricht dem Konstruktor super() von Java. Die Klasse Angestellter kann natürlich auch wieder an eine Klasse vererbt werden. Im Grunde ist nicht einmal die Klasse Mensch die oberste Klasse. Die Klasse Mensch stammt wie alle anderen Klassen auch von der Klasse System.Object ab dies ist die Mutter aller Klassen. Durch diese Vererbung und Weitervererbung entsteht eine Klassenhierarchie, genauso wie die Klassenbibliothek von C# eine Klassenhierarchie ist. 183
Außerdem ist die Vererbung bei Klassen transitiv, d.h., erbt Klasse B von Klasse A und Klasse C von Klasse B, so erbt auch Klasse C von Klasse A. public class Chef : Angestellter { public Chef (char g, string n, int a) : base(g,n,a) { public void VerdoppleGehalt() { Gehalt*=2; Die Klasse Chef leitet sich jetzt von der Klasse Angestellter ab und besitzt nur mehr eine Methode. Um das Ganze als Beispiel zu verwenden, braucht man zu den drei Klassen noch eine Klasse für das Programm selbst. class Kap11_01 { Angestellter a = new Angestellter('m',"Christoph",29); Chef Chefe = new Chef('m',"Bruno",45); Chefe.SetGehalt(20000); Chefe.VerdoppleGehalt(); a.setgehalt(10000); Console.WriteLine(a.GetName()); a.ausgabemitarbeiterinfo(); Console.WriteLine(); Chefe.AusgabeMitarbeiterInfo(); Ausgabe: Christoph Herr Christoph Gehalt 100000 Abteilung: Herr Bruno Gehalt 40000 Abteilung: Für die Klasse Chef haben wir uns jetzt eine Menge Arbeit erspart, da wir alle Methoden und Variablen, die wir verwenden, nur einmal in die Klassen Mensch und Angestellter geschrieben haben. 184
Es ist im Übrigen egal, wie die Klassen im Quelltext positioniert sind. Eine Klasse kann von einer Klasse erben, die im Sourcecode über ihr oder auch unter ihr steht oder auch gar nicht, sondern nur in einer externen Datei oder in der Klassenbibliothek Bei der Verwendung von Vererbung werden auch Zugriffsmodifizierer für Klassen erforderlich. Eine entsprechende Übersicht finden Sie nachfolgend. Modifizierer innerhalb der Klasse innerhalb der abgeleiteten Klasse außerhalb der Klasse public ja ja ja ja internal ja ja ja nein protected ja ja nein nein private ja nein nein nein außerhalb der Assembly 11.2 Überschreiben von Methoden Bei der Programmierung kann es vorkommen, dass Sie eine Methode in einer abgeleiteten Klasse verwenden, die es genau so in der Basisklasse schon gibt. Findet der Compiler in der abgeleiteten Klasse die gleiche Methode wie in der Basisklasse, wird er eine Warnmeldung produzieren. Warnmeldungen können zwar ignoriert werden es wird trotzdem eine EXE-Datei erzeugt, aber man sollte im Hinterkopf behalten, dass Warnmeldungen normalerweise einen Sinn haben. Die Technik des Überschreibens von Methoden nennt man Polymorphismus. using System; public class Kap11_02 { BasisKlasse a; AbgeleiteteKlasse b = new AbgeleiteteKlasse(); a=b; a.macheirgendwas(); b.macheirgendwas(); public class BasisKlasse { public void MacheIrgendwas() { Console.WriteLine("Das ist die Basisklasse"); 185
public class AbgeleiteteKlasse : BasisKlasse { public void MacheIrgendwas() { Console.WriteLine("Das ist die abgeleitete Klasse"); In diesem Programm gibt es sowohl in der Basisklasse als auch in der abgeleiteten Klasse eine Methode namens MacheIrgendwas(). Also produziert der Compiler eine Warnmeldung. kap11_2.cs(21,14): warning CS0108: Das Schlüsselwort new bei AbgeleiteteKlasse.MacheIrgendwas() ist erforderlich, da es das vererbte Element BasisKlasse.MacheIrgendwas() ausblendet. kap11_2.cs(15,14): (Position des Symbols im Verhältnis zur vorherigen Warnung) New Dies ist auch gut so, denn es könnte ja sein, dass Sie die Methode nur aus Versehen überschrieben haben. Wollen Sie die Methode in der abgeleiteten Klasse explizit überschreiben, müssen Sie das Schlüsselwort new vor die Methode setzen. public class AbgeleiteteKlasse : BasisKlasse { new public void MacheIrgendwas() { Console.WriteLine("Das ist die abgeleitete Klasse"); Die Ausgabe des Programms lautet dann: Das ist die Basisklasse Das ist die abgeleitete Klasse Virtual und Override Es gibt allerdings noch eine weitere Möglichkeit, eine Methode zu überschreiben. Dazu wird in der Methodendeklaration in der Basisklasse das Schlüsselwort virtual und in der überschreibenden Methode das Schlüsselwort override benutzt. 186
Man muss allerdings darauf achten, dass durch das Überschreiben einer Methode durch virtual und override nicht nur die Methode der abgeleiteten Klasse ersetzt wird, sondern auch die ursprüngliche Methode der Basisklasse. So wird beispielsweise nach einer expliziten Typkonvertierung eines Objekts der abgeleiteten Klasse in ein Objekt der Basisklasse immer noch die neue Methode aufgerufen. using System; public class Kap11_03 { BasisKlasse a; AbgeleiteteKlasse b = new AbgeleiteteKlasse(); a=b; a.macheirgendwas(); b.macheirgendwas(); public class BasisKlasse { public virtual void MacheIrgendwas() { Console.WriteLine("Das ist die Basisklasse"); public class AbgeleiteteKlasse : BasisKlasse { public override void MacheIrgendwas() { Console.WriteLine("Das ist die abgeleitete Klasse"); Ausgabe: Das ist die abgeleitete Klasse Das ist die abgeleitete Klasse Sie sehen also, durch die Verwendung von virtual und override verändern Sie auch die Basisklasse. 11.3 Abstrakte Klassen Abstrake Klassen und Methoden können nicht direkt instanziert werden. Sie dienen dazu, ein Gerüst für eine Klasse darzustellen, die erst noch komplett geschrieben werden muss. Um eine abstrakte Klasse zu verwenden, ist es also nö- 187
tig, eine neue Klasse zu deklarieren, die von dieser Klasse erbt. Alle abstrakten Methoden der abstrakten Klasse müssen in der neuen Klasse vorhanden sein. public abstract class BasisRechteck { protected int Laenge; protected int Breite; public BasisRechteck(int l, int b) { Laenge=l; Breite=b; public BasisRechteck(int a) { Laenge=a; Breite=a; public abstract void Zeichnen(); public int GetFlaeche() { return l*b; public int GetUmfang() { return 2*(l+b); Diese abstrakte Klasse, bei der nicht alle Methoden implementiert sind, stellt unsere Basisklasse dar. Wenn wir versuchen, sie zu instanzieren, erhalten wir eine Fehlermeldung: public class Kap11_4 { BasisRechteck r = new BasisRechteck(10,20); kap11_4.cs(5,21): error CS0144: Eine Instanz der abstrakten Klasse oder Schnittstelle BasisRechteck konnte nicht erstellt werden. Um diese Klasse zu benutzen, müssen wir erst eine neue Klasse erschaffen, die von der Klasse BasisRechteck erbt. Diese Klasse muss auch noch die Methode Zeichnen() implementieren, da diese in BasisKlasse nur als Gerüst existiert. using System; public class Kap11_04 { 188
Rechteck r = new Rechteck(10,20); Console.WriteLine(r.GetFlaeche()); r.zeichnen(); public class Rechteck : BasisRechteck { public Rechteck (int l, int b) : base(l,b) { public override void Zeichnen() { int i,j; for(i=0;i<laenge;i++) { for(j=0;j<breite;j++) { Console.Write("*"); Console.WriteLine(); Als Implementierung für die Methode Zeichnen verwenden wir das Rechteckzeichnen aus Kapitel 11. Die Klasse Rechteck, die von BasisRechteck abgeleitet ist, lässt sich jetzt problemlos instanzieren. Sinn solcher abstrakten Methoden ist es, den Programmierer, der die Klasse verwendet, an die Implementierung mancher Methoden zu erinnern. In unserem Beispiel stand beim Programmieren der Klasse BasisRechteck noch nicht fest, wie das Rechteck gezeichnet werden sollte. Mit der Klasse Rechteck haben wir eine Implementierung für eine Konsolenanwendung vorgenommen. Hätten wir das Ganze als Windows-Programm, könnten wir immer noch die Klasse BasisRechteck verwenden. Nur müsste jetzt die Methode Zeichnen anders aufgebaut sein eben für eine Windows-Anwendung. Die Implementierung einer abstrakten Methode kann auch mit einer leeren Methode erfolgen, wenn Sie die Methode gar nicht verwenden wollen aber sie muss trotzdem immer implementiert werden. public class Rechteck : BasisRechteck { public Rechteck (int l, int b) : base(l,b) { public override void Zeichnen() { 189
11.4 Versiegelte Klassen Unter versiegelten Klassen versteht man Klassen, von denen man keine weitere Klasse ableiten kann. Der Einsatz einer solchen Klasse, die dem Prinzip der Objektorientierung etwas widerspricht, ist z. B. sinnvoll, wenn kommerzielle Klassen vor einer Veränderung geschützt werden sollen. Eine andere Einsatzmöglichkeit ergibt sich z. B. für eine Debug-Klasse, bei der die versehentliche Weiterverwendung vermieden werden soll. Eine Debug-Klasse dient als Hilfsklasse bei der Programmerstellung und beim Fehlersuchen. Using System; public class Kap11_05 { BasisRechteck r = new BasisRechteck(10,20); r.zeichnen(); public sealed class BasisRechteck { private int Laenge; private int Breite; public BasisRechteck(int l, int b) { Laenge=l; Breite=b; public BasisRechteck(int a) { Laenge=a; Breite=a; public int GetFlaeche() { return Laenge*Breite; public int GetUmfang() { return 2*(Laenge+Breite); public void Zeichnen() { int i,j; for(i=0;i<laenge;i++) { for(j=0;j<breite;j++) { Console.Write("*"); Console.WriteLine(); 190
Wenn Sie jetzt versuchen würden, von der Klasse BasisRechteck die Klasse Rechteck abzuleiten public class Rechteck : BasisRechteck { public Rechteck (int l, int b) : base(l,b) { bekämen Sie die Fehlermeldung kap11_5.cs(11,14): error CS0509: Rechteck : von der versiegelten Klasse BasisRechteck kann nicht geerbt werden kap11_5.cs(18,21): (Position des Symbols im Verhältnis zum vorherigen Fehler) Wenn Sie in einer sealed-klasse Variablen vom Typ protected deklarieren, bekommen Sie natürlich eine Warnmeldung, da diese Variablen dazu gedacht sind, in einer abgeleiteten Klasse weiterverwendet zu werden. Der Compiler geht in diesem Fall davon aus, dass Sie eines der Schlüsselwörter sealed oder protected irrtümlich verwendet haben. Natürlich können Sie nicht abstract und sealed gleichzeitig für eine Klasse verwenden. Das wäre so, als hätten Sie eine Sackgasse, in die nur eine Einbahnstraße führt. 11.5 Interfaces In C# ist es nur möglich, von einer Klasse zu erben. Mehrfachvererbung wie bei C++ gibt es nicht mehr. Die einzige Möglichkeit, doch so etwas wie Mehrfachvererbung zu erreichen, ist der Einsatz von Interfaces (Schnittstellen). Interfaces gleichen abstrakten Klassen, die nur aus abstrakten Methoden bestehen. Allerdings werden Sie nicht mit dem Schlüsselwort class, sondern mit interface eingeleitet. Außerdem sollten Interfaces immer mit einem großen I beginnen. Diesmal ein sehr vereinfachtes Beispiel: using System; public interface ITest { void MacheIrgendwas(); 191