Vererbung und abgeleitete Klassen (C# und Java im Vergleich)
Aktualisiert: November 2007
Sie können die Funktionalität einer vorhandenen Klasse erweitern, indem Sie eine neue Klasse erstellen, die sich von der vorhandenen Klasse ableitet. Die abgeleitete Klasse erbt die Eigenschaften der Basisklasse. Nach Bedarf können Sie Methoden und Eigenschaften hinzufügen oder überschreiben.
In C# werden sowohl die Vererbung als auch die Schnittstellenimplementierung mit dem Operator : definiert, gleichbedeutend mit extends und implements in Java. Die Basisklasse sollte in der Klassendeklaration immer ganz links stehen.
Wie Java unterstützt auch C# keine Mehrfachvererbung. Klassen können maximal von einer Klasse erben. Wie in Java können Sie jedoch für diesen Zweck Schnittstellen verwenden.
Im folgenden Code wird eine Klasse mit dem Namen CoOrds mit den beiden privaten Membervariablen x und y für die Punktposition definiert. Auf diese Variablen kann über die Eigenschaften X bzw. Y zugegriffen werden:
public class CoOrds
{
private int x, y;
public CoOrds() // constructor
{
x = 0;
y = 0;
}
public int X
{
get { return x; }
set { x = value; }
}
public int Y
{
get { return y; }
set { y = value; }
}
}
So leiten Sie eine neue Klasse mit dem Namen ColorCoOrds von der CoOrds-Klasse ab:
public class ColorCoOrds : CoOrds
ColorCoOrds erbt alle Felder und Methoden der Basisklasse. Sie können der abgeleiteten Klasse weitere Felder und Methoden hinzufügen, um nach Bedarf zusätzliche Features bereitzustellen. In diesem Beispiel werden ein privater Member und Accessoren hinzugefügt, damit der Klasse Farbe hinzugefügt werden kann:
public class ColorCoOrds : CoOrds
{
private System.Drawing.Color screenColor;
public ColorCoOrds() // constructor
{
screenColor = System.Drawing.Color.Red;
}
public System.Drawing.Color ScreenColor
{
get { return screenColor; }
set { screenColor = value; }
}
}
Der Konstruktor der abgeleiteten Klasse ruft implizit den Konstruktor der Basisklasse auf (Java-Terminologie: Konstruktor der übergeordneten Klasse). Bei der Vererbung werden alle Basisklassenkonstruktoren vor den Konstruktoren der abgeleiteten Klasse in der Reihenfolge der Klassenhierarchie aufgerufen.
Typumwandlung zu einer Basisklasse
Wie in Java können Sie mit einem Verweis auf eine Basisklasse nicht auf die Member und Methoden einer abgeleiteten Klasse zugreifen. Dies funktioniert auch dann nicht, wenn der Verweis auf die Basisklasse einen gültigen Verweis auf ein Objekt des abgeleiteten Typs enthält.
Sie können auf eine abgeleitete Klasse verweisen, indem Sie implizit auf den abgeleiteten Typ verweisen:
ColorCoOrds color1 = new ColorCoOrds();
CoOrds coords1 = color1;
In diesem Codebeispiel enthält der Basisklassenverweis coords1 eine Kopie des color1-Verweises.
Das base-Schlüsselwort
Mithilfe des base-Schlüsselworts können Sie auf die Basisklassenmember einer Unterklasse zugreifen, selbst wenn die Basismember in der übergeordneten Klasse überschrieben werden. Zum Beispiel können Sie eine abgeleitete Klasse erstellen, die eine Methode mit der gleichen Signatur wie in der Basisklasse enthält. Wenn Sie dieser Methode das new-Schlüsselwort voranstellen, wird sie als vollkommen neue Methode der abgeleiteten Klasse behandelt. Mit dem base-Schlüsselwort können Sie weiterhin eine Methode für den Zugriff auf die ursprüngliche Methode in der Basisklasse bereitstellen.
Angenommen, die Basisklasse CoOrds verfügt über eine Methode mit dem Namen Invert(), die die x-Koordinate und die y-Koordinate vertauscht. Mit dem folgenden Code können Sie diese Methode in der abgeleiteten ColorCoOrds-Klasse ersetzen:
public new void Invert()
{
int temp = X;
X = Y;
Y = temp;
screenColor = System.Drawing.Color.Gray;
}
Wie Sie sehen, tauscht die Methode x und y gegeneinander aus und legt anschließend die Farbe der Koordinaten auf grau fest. Um Zugriff auf die Basisimplementierung dieser Methode zu ermöglichen, können Sie in ColorCoOrds eine Methode wie die folgende erstellen:
public void BaseInvert()
{
base.Invert();
}
In einem ColorCoOrds-Objekt können Sie dann die Basismethode über die BaseInvert()-Methode aufrufen.
ColorCoOrds color1 = new ColorCoOrds();
color1.BaseInvert();
Beachten Sie, dass Sie das gleiche Ergebnis erzielen, wenn Sie einer Instanz von ColorCoOrds einen Verweis auf die Basisklasse zuordnen und dann auf die entsprechenden Methoden zugreifen:
CoOrds coords1 = color1;
coords1.Invert();
Auswählen von Konstruktoren
Basisklassenobjekte werden grundsätzlich vor abgeleiteten Klassen erstellt. Folglich wird der Konstruktor der Basisklasse vor dem Konstruktor der abgeleiteten Klasse ausgeführt. Wenn die Basisklasse über mehr als einen Konstruktor verfügt, entscheidet die abgeleitete Klasse darüber, welcher Konstruktor aufgerufen wird. Zum Beispiel können Sie die CoOrds-Klasse ändern, um einen zweiten Konstruktor hinzuzufügen:
public class CoOrds
{
private int x, y;
public CoOrds()
{
x = 0;
y = 0;
}
public CoOrds(int x, int y)
{
this.x = x;
this.y = y;
}
}
Dann können Sie mithilfe des base-Schlüsselworts die ColorCoOrds-Klasse so ändern, dass sie einen bestimmten Konstruktor verwendet:
public class ColorCoOrds : CoOrds
{
public System.Drawing.Color color;
public ColorCoOrds() : base ()
{
color = System.Drawing.Color.Red;
}
public ColorCoOrds(int x, int y) : base (x, y)
{
color = System.Drawing.Color.Red;
}
}
In Java ist diese Funktionalität über das super-Schlüsselwort implementiert.
Überschreiben von Methoden
Eine abgeleitete Klasse kann die Methode einer Basisklasse überschreiben, indem sie eine neue Implementierung für die deklarierte Methode bereitstellt. Ein entscheidender Unterschied zwischen Java und C# besteht darin, dass Methoden in Java standardmäßig als virtuell gekennzeichnet werden. Methoden in C# müssen dagegen mit dem virtual-Modifizierer explizit als virtuell gekennzeichnet werden. Eigenschaftenaccessoren können auf ähnliche Weise wie Methoden überschrieben werden.
Virtuelle Methoden
Eine Methode, die in einer abgeleiteten Klasse überschrieben werden soll, wird mit dem virtual-Modifizierer deklariert. In einer abgeleiteten Klasse wird die überschriebene Methode mit dem override-Modifizierer deklariert.
Der override-Modifizierer kennzeichnet die Methode oder Eigenschaft einer abgeleiteten Klasse, die eine Methode bzw. Eigenschaft der Basisklasse mit dem gleichen Namen und der gleichen Signatur ersetzt. Die zu überschreibende Basismethode muss als virtual, abstract oder override deklariert werden. Eine statische oder nicht virtuelle Methode kann nicht auf diese Weise überschrieben werden. Sowohl die überschriebene als auch die überschreibende Methode oder Eigenschaft müssen über die gleichen Zugriffsebenenmodifizierer verfügen.
Im folgenden Beispiel wird eine virtuelle Methode mit dem Namen StepUp gezeigt, die in einer abgeleiteten Klasse mit dem override-Modifizierer überschrieben wird:
public class CountClass
{
public int count;
public CountClass(int startValue) // constructor
{
count = startValue;
}
public virtual int StepUp()
{
return ++count;
}
}
class Count100Class : CountClass
{
public Count100Class(int x) : base(x) // constructor
{
}
public override int StepUp()
{
return ((base.count) + 100);
}
}
class TestCounters
{
static void Main()
{
CountClass counter1 = new CountClass(1);
CountClass counter100 = new Count100Class(1);
System.Console.WriteLine("Count in base class = {0}", counter1.StepUp());
System.Console.WriteLine("Count in derived class = {0}", counter100.StepUp());
}
}
Beim Ausführen des Codes werden Sie feststellen, dass der Konstruktor der abgeleiteten Klasse den Methodentext der Basisklasse verwendet und Sie den count-Member initialisieren können, ohne den Code zu duplizieren. Die Ausgabe lautet wie folgt:
Count in base class = 2
Count in derived class = 101
Abstrakte Klassen
Eine abstrakte Klasse deklariert eine oder mehrere Methoden bzw. Eigenschaften als abstrakt. Solche Methoden sind nicht in der deklarierenden Klasse implementiert. Dennoch kann eine abstrakte Klasse auch nicht-abstrakte Methoden enthalten, also Methoden, für die bereits in der deklarierenden Klasse eine Implementierung bereitgestellt wird. Eine abstrakte Klasse kann nicht direkt instanziiert werden, sondern nur als abgeleitete Klasse. Derart abgeleitete Klassen müssen über das override-Schlüsselwort Implementierungen für alle abstrakten Methoden und Eigenschaften bereitstellen, es sei denn, der abgeleitete Member wird selbst als abstrakt deklariert.
Im folgenden Beispiel wird eine abstrakte Klasse mit dem Namen Employee deklariert: Außerdem wird eine abgeleitete Klasse mit dem Namen Manager erstellt, die eine Implementierung der abstrakten Show()-Methode bereitstellt, die in der Employee-Klasse definiert ist:
public abstract class Employee
{
protected string name;
public Employee(string name) // constructor
{
this.name = name;
}
public abstract void Show(); // abstract show method
}
public class Manager: Employee
{
public Manager(string name) : base(name) {} // constructor
public override void Show() //override the abstract show method
{
System.Console.WriteLine("Name : " + name);
}
}
class TestEmployeeAndManager
{
static void Main()
{
// Create an instance of Manager and assign it to a Manager reference:
Manager m1 = new Manager("H. Ackerman");
m1.Show();
// Create an instance of Manager and assign it to an Employee reference:
Employee ee1 = new Manager("M. Knott");
ee1.Show(); //call the show method of the Manager class
}
}
Mithilfe dieses Codes werden die von der Manager-Klasse bereitgestellte Implementierung von Show() aufgerufen und die Mitarbeiternamen auf dem Bildschirm ausgegeben. Die Ausgabe lautet wie folgt:
Name : H. Ackerman
Name : M. Knott
Schnittstellen
Eine Schnittstelle ist eine Art Skelettklasse, die Methodensignaturen, aber keine Methodenimplementierungen enthält. Unter diesem Aspekt verhalten sich Schnittstellen wie abstrakte Klassen, die nur abstrakte Methoden enthalten. C#-Schnittstellen sind Java-Schnittstellen sehr ähnlich und funktionieren nahezu gleich.
Alle Member einer Schnittstelle sind definitionsgemäß öffentlich. Eine Schnittstelle kann keine Konstanten, Felder (private Datenmember), Konstruktoren, Destruktoren oder andere statische Membertypen enthalten. Wenn ein Modifizierer für die Member einer Schnittstelle angegeben wird, generiert der Compiler einen Fehler.
Sie können Klassen von einer Schnittstelle ableiten, um diese Schnittstelle zu implementieren. Solche abgeleiteten Klassen müssen Implementierungen für alle Methoden der Schnittstelle bereitstellen, außer wenn die abgeleitete Klasse als abstrakt deklariert wird.
Eine Schnittstelle wird genauso wie in Java deklariert. Eine Eigenschaft in einer Schnittstellendefinition gibt nur den Eigenschaftentyp an und legt fest, ob die Eigenschaft lediglich mithilfe des get-Schlüsselworts bzw. des set-Schlüsselworts schreibgeschützt, lesegeschützt oder mit Lese- und Schreibzugriff aufgerufen werden kann. In der folgenden Schnittstelle wird eine schreibgeschützte Eigenschaft deklariert:
public interface ICDPlayer
{
void Play(); // method signature
void Stop(); // method signature
int FastForward(float numberOfSeconds);
int CurrentTrack // read-only property
{
get;
}
}
Damit eine Klasse von dieser Schnittstelle erben kann, müssen Sie anstelle des implements-Schlüsselworts in Java einen Doppelpunkt verwenden. Die implementierende Klasse muss Definitionen für alle Methoden und alle erforderlichen Eigenschaftenaccessoren bereitstellen:
public class CDPlayer : ICDPlayer
{
private int currentTrack = 0;
// implement methods defined in the interface
public void Play()
{
// code to start CD...
}
public void Stop()
{
// code to stop CD...
}
public int FastForward(float numberOfSeconds)
{
// code to fast forward CD using numberOfSeconds...
return 0; //return success code
}
public int CurrentTrack // read-only property
{
get
{
return currentTrack;
}
}
// Add additional methods if required...
}
Implementieren von mehreren Schnittstellen
Mithilfe der folgenden Syntax kann eine Klasse mehrere Schnittstellen implementieren:
public class CDAndDVDComboPlayer : ICDPlayer, IDVDPlayer
Wenn eine Klasse mehr als eine Schnittstelle implementiert und es zu Mehrdeutigkeiten bei den Membernamen kommt, werden die Eigenschaften- oder Methodennamen mit dem vollständigen Qualifizierer aufgelöst. Mit anderen Worten: In der abgeleiteten Klasse kann der Konflikt aufgelöst werden, indem der vollqualifizierte Name der Methode verwendet und so die zugehörige Schnittstelle angegeben wird, wie in ICDPlayer.Play().
Siehe auch
Konzepte
Referenz
Vererbung (C#-Programmierhandbuch)