Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort zur 6. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Das Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Strukturen und Enumerationen
7 Fehlerbehandlung und Debugging
8 Auflistungsklassen (Collections)
9 Generics – Generische Datentypen
10 Weitere C#-Sprachfeatures
11 LINQ
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 XML
15 Multithreading und die Task Parallel Library (TPL)
16 Einige wichtige .NET-Klassen
17 Projektmanagement und Visual Studio 2012
18 Einführung in die WPF und XAML
19 WPF-Layout-Container
20 Fenster in der WPF
21 WPF-Steuerelemente
22 Elementbindungen
23 Konzepte von WPF
24 Datenbindung
25 Weitere Möglichkeiten der Datenbindung
26 Dependency Properties
27 Ereignisse in der WPF
28 WPF-Commands
29 Benutzerdefinierte Controls
30 2D-Grafik
31 ADO.NET – Verbindungsorientierte Objekte
32 ADO.NET – Das Command-Objekt
33 ADO.NET – Der SqlDataAdapter
34 ADO.NET – Daten im lokalen Speicher
35 ADO.NET – Aktualisieren der Datenbank
36 Stark typisierte DataSets
37 Einführung in das ADO.NET Entity Framework
38 Datenabfragen des Entity Data Models (EDM)
39 Entitätsaktualisierung und Zustandsverwaltung
40 Konflikte behandeln
41 Plain Old CLR Objects (POCOs)
Stichwort

Download:
- Beispiele, ca. 62,4 MB

Buch bestellen
Ihre Meinung?

Spacer
Visual C# 2012 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2012

Visual C# 2012
Rheinwerk Computing
1402 S., 6., aktualisierte und erweiterte Auflage 2013, geb., mit DVD
49,90 Euro, ISBN 978-3-8362-1997-6
Pfeil 4 Vererbung, Polymorphie und Interfaces
Pfeil 4.1 Die Vererbung
Pfeil 4.1.1 Basisklassen und abgeleitete Klassen
Pfeil 4.1.2 Die Ableitung einer Klasse
Pfeil 4.1.3 Klassen, die nicht abgeleitet werden können
Pfeil 4.1.4 Konstruktoren in abgeleiteten Klassen
Pfeil 4.1.5 Der Zugriffsmodifizierer »protected«
Pfeil 4.1.6 Die Konstruktorverkettung in der Vererbung
Pfeil 4.2 Der Problemfall geerbter Methoden
Pfeil 4.2.1 Geerbte Methoden mit »new« verdecken
Pfeil 4.2.2 Abstrakte Methoden
Pfeil 4.2.3 Virtuelle Methoden
Pfeil 4.3 Typumwandlung und Typuntersuchung von Objektvariablen
Pfeil 4.3.1 Die implizite Typumwandlung von Objektreferenzen
Pfeil 4.3.2 Die explizite Typumwandlung von Objektreferenzen
Pfeil 4.3.3 Typuntersuchung mit dem »is«-Operator
Pfeil 4.3.4 Typumwandlung mit dem »as«-Operator
Pfeil 4.4 Polymorphie
Pfeil 4.4.1 Die »klassische« Methodenimplementierung
Pfeil 4.4.2 Abstrakte Methoden
Pfeil 4.4.3 Virtuelle Methoden
Pfeil 4.5 Weitere Gesichtspunkte der Vererbung
Pfeil 4.5.1 Versiegelte Methoden
Pfeil 4.5.2 Überladen einer Basisklassenmethode
Pfeil 4.5.3 Statische Member und Vererbung
Pfeil 4.5.4 Geerbte Methoden ausblenden?
Pfeil 4.6 Das Projekt »GeometricObjectsSolution« ergänzen
Pfeil 4.6.1 Die Klasse »GeometricObject«
Pfeil 4.7 Eingebettete Klassen (Nested Classes)
Pfeil 4.8 Interfaces (Schnittstellen)
Pfeil 4.8.1 Einführung in die Schnittstellen
Pfeil 4.8.2 Die Schnittstellendefinition
Pfeil 4.8.3 Die Schnittstellenimplementierung
Pfeil 4.8.4 Die Interpretation der Schnittstellen
Pfeil 4.8.5 Änderungen am Projekt »GeometricObjects«
Pfeil 4.9 Das Zerstören von Objekten – der »Garbage Collector«
Pfeil 4.9.1 Die Arbeitsweise des Garbage Collectors
Pfeil 4.9.2 Expliziter Aufruf des Garbage Collectors
Pfeil 4.9.3 Der Destruktor
Pfeil 4.9.4 Die »IDisposable«-Schnittstelle
Pfeil 4.9.5 Die Ergänzungen in den Klassen »Circle« und »Rectangle«

4 Vererbung, Polymorphie und InterfacesZur nächsten Überschrift


Galileo Computing - Zum Seitenanfang

4.1 Die VererbungZur nächsten ÜberschriftZur vorigen Überschrift

Die objektorientierte Programmierung baut auf drei Säulen auf: Datenkapselung, Vererbung und Polymorphie. Viele Entwickler sprechen sogar von vier Säulen, weil sie die Klassendefinition mit einbeziehen. Über Letzteres lässt sich trefflich diskutieren, da eine Klassendefinition selbst wieder das Fundament der anderen drei Säulen ist. Aber wie dem auch sei, zwei Säulen bleiben für uns noch übrig, nämlich die Vererbung und die Polymorphie. Beiden wollen wir uns in diesem Kapitel widmen.


Galileo Computing - Zum Seitenanfang

4.1.1 Basisklassen und abgeleitete KlassenZur nächsten ÜberschriftZur vorigen Überschrift

Welche Fähigkeit würden Sie von einem Circle-Objekt neben den bereits implementierten Fähigkeiten noch erwarten? Wahrscheinlich eine ganz wesentliche, nämlich die Fähigkeit, sich in einer beliebigen grafikfähigen Komponente visualisieren zu können. Bisher fehlt dazu noch eine passende Methode.

Die Klasse Circle soll jedoch von uns als abgeschlossen betrachtet werden. Damit simulieren wir zwei Ausgangssituationen, die in der täglichen Praxis häufig auftreten:

  • Die Implementierung einer Klasse, wie beispielsweise Circle, ist für viele Anwendungsfälle völlig ausreichend. Eine Ergänzung der Memberliste würde nicht allgemeinen, sondern nur speziellen Zusatzanforderungen genügen.
  • Die Klasse liegt im kompilierten Zustand vor. Damit besteht auch keine Möglichkeit, den Quellcode der Klasse um weitere Fähigkeiten zu ergänzen.

Wie kann das Problem gelöst werden, eine Klasse um zusätzliche Fähigkeiten zu erweitern, damit sie weiter gehenden Anforderungen gewachsen ist?

Die Antwort ist sehr einfach und lautet: Es muss eine weitere Klasse entwickelt werden. Diese soll im weiteren Verlauf GraphicCircle heißen. Die zusätzliche Klasse soll einerseits alle Fähigkeiten der Klasse Circle haben und darüber hinaus auch noch eine Methode namens Draw, um das Objekt zu zeichnen. Mit der Vererbung, einer der eingangs erwähnten Säulen der objektorientierten Programmierung, ist die Lösung sehr einfach zu realisieren.

Eine Klasse, die ihre Member als Erbgut einer abgeleiteten Klasse zur Verfügung stellt, wird als Basisklasse bezeichnet. Die erbende Klasse ist die Subklasse oder einfach nur die abgeleitete Klasse. Dem Grundprinzip der Vererbung folgend, verfügen abgeleitete Klassen normalerweise über mehr Funktionalitäten als ihre Basisklasse.

Zwei Klassen, die miteinander in einer Vererbungsbeziehung stehen, werden, wie in Abbildung 4.1 gezeigt, durch einen Beziehungspfeil von der abgeleiteten Klasse in Richtung der Basisklasse dargestellt.

Abbildung

Abbildung 4.1 Die Vererbungsbeziehung zwischen den Klassen »Circle« und »GraphicCircle«

Die Vererbungslinie ist nicht zwangsläufig mit dem Ableiten einer Klasse aus einer Basisklasse beendet. Eine Subklasse kann ihrerseits selbst zur Basisklasse mutieren, wenn sie selbst abgeleitet wird. Es ist auch möglich, von einer Klasse mehrere Subklassen abzuleiten, die dann untereinander beziehungslos sind. Am Ende kann dadurch eine nahezu beliebig tiefe und weit verzweigte Vererbungshierarchie entstehen, die einer Baumstruktur ähnelt.

Jeder Baum hat einen Stamm. Genauso sind auch alle Klassen von .NET auf eine allen gemeinsame Klasse zurückzuführen: Object. Diese Klasse ist die einzige in der .NET-Klassenbibliothek, die selbst keine Basisklasse hat. Geben Sie bei einer Klassendefinition keine Basisklasse explizit an, ist Object immer die direkte Basisklasse. Deshalb finden Sie in der IntelliSense-Hilfe auch immer die Methoden Equals, GetType, ToString und GetHashCode, die aus Object geerbt werden.

Prinzipiell wird in der Objektorientierung zwischen der Einfach- und der Mehrfachvererbung unterschieden. Bei der einfachen Vererbung hat eine Klasse nur eine direkte Basisklasse, bei der Mehrfachvererbung können es mehrere sein. Eine Klassenhierarchie, die auf Mehrfachvererbung basiert, ist komplex und kann unter Umständen zu unerwarteten Nebeneffekten führen. Um solchen Konflikten aus dem Weg zu gehen, wird die Mehrfachvererbung von .NET nicht unterstützt. Damit werden einerseits zwar bewusst Einschränkungen in Kauf genommen, die aber andererseits durch die Schnittstellen (interfaces) nahezu gleichwertig ersetzt werden. Das Thema der Interfaces wird uns später in diesem Kapitel noch beschäftigen.


Galileo Computing - Zum Seitenanfang

4.1.2 Die Ableitung einer KlasseZur nächsten ÜberschriftZur vorigen Überschrift

Wenden wir uns nun wieder unserem Beispiel zu, und ergänzen wir das Projekt GeometricObjectsSolution um die Klasse GraphicCircle, die die Klasse Circle ableiten soll. Zudem soll GraphicCircle um die typspezifische Methode Draw erweitert werden. Die Ableitung wird in der neuen Klassendefinition durch einen Doppelpunkt und die sich daran anschließende Bekanntgabe der Basisklasse zum Ausdruck gebracht:

public class GraphicCircle : Circle 
{
public void Draw()
{
Console.WriteLine("Der Kreis wird gezeichnet");
}
}

Listing 4.1 Die Definition der abgeleiteten Klasse »GraphicCircle«

Wir wollen an dieser Stelle das Kreisobjekt nicht wirklich zeichnen, sondern nur stellvertretend eine Zeichenfolge an der Konsole ausgeben.

Die Konsequenz der Vererbung können Sie zu diesem Zeitpunkt bereits sehen, wenn Sie ein Objekt des Typs GraphicCircle mit

GraphicCircle gCircle = new GraphicCircle();

erzeugen und danach die Punktnotation auf den Objektverweis anwenden: In der IntelliSense-Hilfe werden neben der neuen Methode Draw alle öffentlichen Mitglieder der Klasse Circle angezeigt, obwohl diese in der abgeleiteten Klasse nicht definiert sind (siehe Abbildung 4.2). Natürlich fehlen auch nicht die aus Object geerbten Methoden, die ebenfalls über die »Zwischenstation« Circle zu Mitgliedern der Klasse GraphicCircle werden.

Abbildung

Abbildung 4.2 Die von der Klasse »Circle« geerbten Fähigkeiten

Die Tatsache, dass ein Objekt vom Typ GraphicCircle alle Komponenten der Klasse Circle offenlegt, lässt unweigerlich den Schluss zu, dass das Objekt einer abgeleiteten Klasse auch gleichzeitig ein Objekt der Basisklasse sein muss. Zwischen den beiden in der Vererbungshierarchie in Beziehung stehenden Klassen existiert eine Beziehung, die als Ist-ein(e)-Beziehung bezeichnet wird.

Ein Objekt vom Typ einer abgeleiteten Klasse ist gleichzeitig auch immer ein Objekt vom Typ seiner Basisklasse.

Das bedeutet konsequenterweise, dass ein Objekt vom Typ GraphicCircle gleichzeitig auch ein Objekt vom Typ Object ist – so wie auch ein Circle-Objekt vom Typ Object ist. Letztendlich ist alles im .NET Framework vom Typ Object. Eine weitere wichtige Schlussfolgerung kann ebenfalls daraus gezogen werden: In Richtung der Basisklassen werden die Objekte immer allgemeiner, in Richtung der abgeleiteten Klassen immer spezialisierter.

Die Aussage, dass es sich bei der Vererbung um die codierte Darstellung einer Ist-ein(e)-Beziehung handelt, sollten Sie sich sehr gut einprägen. Es hilft dabei, Vererbungshierarchien sinnvoll und realitätsnah umzusetzen. Sie werden dann sicher nicht auf die Idee kommen, aus einem Elefanten eine Mücke abzuleiten, nur weil der Elefant vier Beine hat und eine Mücke sechs. Sie würden in dem Sinne zwar aus einer Mücke einen Elefanten machen, aber eine Mücke ist nicht gleichzeitig ein Elefant ...


Galileo Computing - Zum Seitenanfang

4.1.3 Klassen, die nicht abgeleitet werden könnenZur nächsten ÜberschriftZur vorigen Überschrift

Klassen, die als »sealed« definiert sind

Klassen, die abgeleitet werden, vererben den abgeleiteten Klassen ihre Eigenschaften und Methoden. Es kommt aber immer wieder vor, dass die weitere Ableitung einer Klasse keinen Sinn ergibt oder sogar strikt unterbunden werden muss, weil die von der Klasse zur Verfügung gestellten Dienste als endgültig betrachtet werden.

Um sicherzustellen, dass eine Klasse nicht weiter abgeleitet werden kann, wird die Klassendefinition um den Modifizierer sealed ergänzt:

public sealed class GraphicCircle {[...]}

Statische Klassen und Vererbung

Neben sealed-Klassen sind auch statische Klassen nicht vererbungsfähig. Darüber hinaus dürfen statische Klassen auch nicht aus einer beliebigen Klasse abgeleitet werden. Die einzig mögliche Basisklasse ist Object.


Galileo Computing - Zum Seitenanfang

4.1.4 Konstruktoren in abgeleiteten KlassenZur nächsten ÜberschriftZur vorigen Überschrift

Bei der Erzeugung des Objekts einer abgeleiteten Klasse gelten dieselben Regeln wie beim Erzeugen des Objekts einer Basisklasse:

  • Es wird generell ein Konstruktor aufgerufen.
  • Der Subklassenkonstruktor darf überladen werden.

Konstruktoren werden grundsätzlich nicht von der Basisklasse an die Subklasse weitervererbt. Daher müssen alle erforderlichen bzw. gewünschten Konstruktoren in der abgeleiteten Klasse definiert werden. Das gilt auch für den statischen Initialisierer. Abgesehen vom impliziten, parameterlosen Standardkonstruktor

public GraphicCircle(){}

ist die Klasse GraphicCircle daher noch ohne weiteren Konstruktor. Um dem Anspruch zu genügen, einem Circle-Objekt auch hinsichtlich der Instanziierbarkeit gleichwertig zu sein, benötigen wir insgesamt drei Konstruktoren, die in der Lage sind, entweder den Radius oder den Radius samt den beiden Bezugspunktkoordinaten entgegenzunehmen. Außerdem müssen wir berücksichtigen, dass Objekte vom Typ GraphicCircle gleichzeitig Objekte vom Typ Circle sind. Die logische Konsequenz ist, den Objektzähler mit jedem neuen GraphicCircle-Objekt zu erhöhen. Mit diesen Vorgaben, die identisch mit denen in der Basisklasse sind, sieht der erste und, wie Sie noch sehen werden, etwas blauäugige und sogar naive Entwurf der Erstellungsroutinen in der Klasse GraphicCircle zunächst wie in Listing 4.2 gezeigt aus:

public class GraphicCircle : Circle {

public GraphicCircle() : this(0, 0, 0) { }

public GraphicCircle(int radius) : this(radius, 0, 0) { }

public GraphicCircle(int radius, double x, double y) {
Radius = radius;
XCoordinate = x;
YCoordinate = y;
Circle._CountCircles++;
}
}

Listing 4.2 Erste Idee der Konstruktorüberladung in »GraphicCircle«

Der Versuch, diesen Programmcode zu kompilieren, endet jedoch in einem Fiasko, denn der C#-Compiler kann das Feld _CountCircles nicht erkennen und verweigert deswegen die Kompilierung. Der Grund hierfür ist recht einfach: Das Feld ist in der Basisklasse Circle private deklariert. Private Member sind aber grundsätzlich nur in der Klasse sichtbar, in der sie deklariert sind. Obwohl aus objektorientierter Sicht ein Objekt vom Typ GraphicCircle auch gleichzeitig ein Objekt vom Typ Circle ist, kann die strikte Kapselung einer privaten Variablen durch die Vererbung nicht aufgebrochen werden. Nur der Code in der Klasse Circle hat Zugriff auf die in dieser Klasse definierten privaten Klassenmitglieder.


Galileo Computing - Zum Seitenanfang

4.1.5 Der Zugriffsmodifizierer »protected«Zur nächsten ÜberschriftZur vorigen Überschrift

Einen Ausweg aus diesem Dilemma, ein Klassenmitglied einerseits gegen den unbefugten Zugriff von außen zu schützen, es aber andererseits in einer abgeleiteten Klasse sichtbar zu machen, bietet der Zugriffsmodifizierer protected. Member, die als protected deklariert sind, verhalten sich ähnlich wie private deklarierte: Sie verhindern den unzulässigen Zugriff von außerhalb, garantieren jedoch andererseits, dass in einer abgeleiteten Klasse direkt darauf zugegriffen werden kann.

Diese Erkenntnis führt zu einem Umdenken bei der Implementierung einer Klasse: Muss davon ausgegangen werden, dass die Klasse als Basisklasse ihre Dienste zur Verfügung stellt, sind alle privaten Member, die einer abgeleiteten Klasse zur Verfügung stehen sollen, protected zu deklarieren. Daher müssen (oder besser »sollten« – siehe dazu auch die Anmerkung weiter unten) wir in der Klasse Circle noch folgende Änderungen vornehmen:

// Änderung der privaten Felder in der Klasse Circle 

protected int _Radius;
protected static int _CountCircles;

Erst jetzt vererbt die Klasse Circle alle Member an die Ableitung GraphicCircle, und der C#-Compiler wird keinen Fehler mehr melden.

Selbstverständlich könnte man an dieser Stelle auch argumentieren, dass der Modifikator private eines Feldes aus der Überlegung heraus gesetzt worden ist, mögliche unzulässige Werte von vornherein zu unterbinden und – zumindest im Fall unseres Radius – den Zugang nur über get und set der Eigenschaftsmethode zu erzwingen. Andererseits kann man dem auch entgegenhalten, dass man bei der Bereitstellung einer ableitbaren Klasse nicht weiß, welche Intention hinter der Ableitung eine wichtige Rolle spielt. Mit dieser Argumentation ist eine »Aufweichung« des gekapselten Zugriffs durch protected durchaus vertretbar. In einer so geführten Diskussion muss dann aber auch noch ein weiterer Gesichtspunkt angeführt werden: Die Eigenschaftsmethode kann in einer ableitbaren Klasse auch neu implementiert werden. Darauf kommen wir später in diesem Kapitel noch zu sprechen.

Was also ist zu tun? private oder protected? Eine allgemeingültige Antwort gibt es nicht. Im Einzelfall müssen Sie selbst entscheiden, welchen Zugriffsmodifikator Sie für das Feld benutzen. Einfacher gestaltet sich die Diskussion nur hinsichtlich der Methoden. Wenn Sie den Zugriff aus einer abgeleiteten Klasse heraus auf eine Methode nicht wünschen, muss sie private definiert werden, ansonsten protected.


Galileo Computing - Zum Seitenanfang

4.1.6 Die Konstruktorverkettung in der VererbungZur nächsten ÜberschriftZur vorigen Überschrift

Wir wollen nun die Implementierung in Main testen, indem wir ein Objekt des Typs GraphicCircle erzeugen und uns den Stand des Objektzählers, der von der Circle-Klasse geerbt wird, an der Konsole ausgeben lassen. Der Code dazu lautet:

static void Main(string[] args) {
GraphicCircle gc = new GraphicCircle();
Console.WriteLine("Anzahl der Kreise = {0}", GraphicCircle.CountCircles);
}

Listing 4.3 Testen der Konstruktoren von »GraphicCircle« mit unerwartetem Resultat

Völlig unerwartet werden wir mit folgender Situation konfrontiert: Mit

Anzahl der Kreise = 2

wird uns suggeriert, wir hätten zwei Kreisobjekte erzeugt, obwohl wir doch tatsächlich nur einmal den new-Operator benutzt haben und sich folgerichtig auch nur ein konkretes Objekt im Speicher befinden kann.

Das Ergebnis ist falsch und beruht auf der bisher noch nicht berücksichtigten Aufrufverkettung zwischen den Sub- und Basisklassenkonstruktoren. Konstruktoren werden bekanntlich nicht vererbt und müssen deshalb – falls erforderlich – in jeder abgeleiteten Klasse neu definiert werden. Dennoch kommt den Konstruktoren der Basisklasse eine entscheidende Bedeutung zu. Bei der Initialisierung eines Subklassenobjekts wird nämlich in jedem Fall zuerst ein Basisklassenkonstruktor aufgerufen. Es kommt zu einer Top-down-Verkettung der Konstruktoren, angefangen bei der obersten Basisklasse (Object) bis hinunter zu der Klasse, deren Konstruktor aufgerufen wurde (siehe Abbildung 4.3).

Die Verkettung der Konstruktoraufrufe dient dazu, zunächst die geerbten Komponenten der Basisklasse zu initialisieren. Erst danach wird der Konstruktor der direkten Subklasse ausgeführt, der eigene Initialisierungen vornehmen kann und gegebenenfalls auch die Vorinitialisierung der geerbten Komponenten an die spezifischen Bedürfnisse der abgeleiteten Klasse anpasst. Standardmäßig wird dabei immer zuerst der parameterlose Konstruktor der Basisklasse aufgerufen.

Abbildung

Abbildung 4.3 Die Verkettung der Konstruktoraufrufe in einer Vererbungshierarchie

Die Konstruktorverkettung hat maßgeblichen Einfluss auf die Modellierung einer Klasse, die parametrisierte Konstruktoren enthält. Eine »konstruktorlose« Klasse hat grundsätzlich immer einen impliziten, parameterlosen Konstruktor. Ergänzt man jedoch eine Klasse um einen parametrisierten Konstruktor, existiert der implizite, parameterlose nicht mehr. Wird nun das Objekt einer abgeleiteten Klasse erzeugt, kommt es zum Aufruf des parameterlosen Konstruktors der Basisklasse. Wird dieser durch parametrisierte Konstruktoren überschrieben und nicht explizit codiert, meldet der Compiler einen Fehler.

Sie sollten sich dessen bewusst sein, wenn Sie eine ableitbare Klasse entwickeln und parametrisierte Konstruktoren hinzufügen. Das Problem ist sehr einfach zu lösen, indem Sie einen parameterlosen Konstruktor in der Basisklasse definieren.

Die Konstruktorverkettung mit »base« steuern

Nun erklärt sich auch das scheinbar unsinnige Ergebnis des Objektzählers im vorhergehenden Abschnitt, der bei der Instanziierung eines Objekts vom Typ GraphicCircle behauptete, zwei Kreisobjekte würden vorliegen, obwohl es nachweislich nur ein einziges sein konnte. Durch die Konstruktorverkettung wird zunächst der parameterlose Konstruktor der Basisklasse Circle aufgerufen, danach der der Klasse GraphicCircle. In beiden wird der Objektzähler erhöht, was letztendlich zu einem falschen Zählerstand führt. Die Ursache des Problems ist die Duplizität der Implementierung der beiden parameterlosen Konstruktoren, nämlich in Circle:

public Circle(...) {
[...]
Circle._CountCircles++;
}

und in der von Circle abgeleiteten Klasse GraphicCircle:

public GraphicCircle(...) {
[...]
Circle._CountCircles++;
}

Betrachten wir noch einmal die Implementierung der Konstruktoren in GraphicCircle: Alle Konstruktoraufrufe werden derzeit mit this an den Konstruktor mit den meisten Parametern weitergeleitet. Bei der Erzeugung eines GraphicCircle-Objekts wird zudem standardmäßig der parameterlose der Klasse Circle aufgerufen, der den Aufruf seinerseits intern an den dreifach parametrisierten in dieser Klasse weiterleitet. Außerdem entspricht der Code in den Konstruktoren von GraphicCircle exakt dem Code in den gleich parametrisierten Konstruktoren in Circle.

Optimal wäre es, anstelle des klassenintern weiterleitenden this-Aufrufs in GraphicCircle den Aufruf direkt an den gleich parametrisierten Konstruktor der Basisklasse Circle zu delegieren. Dabei müssten die dem Konstruktor übergebenen Argumente an den gleich parametrisierten Konstruktor der Basisklasse weitergeleitet werden.

C# bietet eine solche Möglichkeit mit dem Schlüsselwort base an. Mit base kann der Konstruktoraufruf einer Klasse an einen bestimmten Konstruktor der direkten Basisklasse umgeleitet werden. base wird dabei genauso wie this eingesetzt, das heißt, Sie können base Argumente übergeben, um einen bestimmten Konstruktor in der Basis anzusteuern.

Das objektorientierte Paradigma schreibt vor, dass aus einer abgeleiteten Klasse heraus mittels Aufrufverkettung zuerst immer ein Konstruktor der Basisklasse ausgeführt werden muss. Per Vorgabe ist das bekanntermaßen der parameterlose. Mit base können wir die implizite, standardmäßige Konstruktorverkettung durch eine explizite ersetzen und die Steuerung selbst übernehmen: Es kommt zu keinem weiteren impliziten Aufruf des parameterlosen Basisklassenkonstruktors mehr.

In unserem Beispiel der Klasse Circle bietet es sich sogar an, sofort den dreifach parametrisierten Konstruktor der Basis aufzurufen. Sehen wir uns nun die überarbeitete Fassung der GraphicCircle-Konstruktoren an:

public GraphicCircle : base(0, 0, 0) { }

public GraphicCircle(int radius) : base(radius, 0, 0) { }

public GraphicCircle(int radius, double x, double y) : base(radius, x, y){ }

Listing 4.4 Die endgültige Version der Konstruktoren in »GraphicCircle«

Schreiben wir jetzt eine Testroutine, z. B.:

GraphicCircle gCircle = new GraphicCircle();
Console.WriteLine("Anzahl der Kreise = {0}", GraphicCircle.CountCircles);

Jetzt wird die Ausgabe des Objektzählers tatsächlich den korrekten Stand wiedergeben.

Der Zugriff auf die Member der Basisklasse mit »base«

Mit base kann nicht nur die Konstruktorverkettung explizit gesteuert werden. Sie können dieses Schlüsselwort auch dazu benutzen, um innerhalb einer abgeleiteten Klasse auf Member der Basisklasse zuzugreifen, solange sie nicht private deklariert sind. Dabei gilt, dass die Methode der Basisklasse, auf die zugegriffen wird, durchaus eine von dieser Klasse selbst geerbte Methode sein kann, also aus Sicht der base-implementierenden Subklasse aus einer indirekten Basisklasse stammt, beispielsweise:

class BaseClass {
public void DoSomething() {
Console.WriteLine("In 'BaseClass.DoSomething()'");
}
}

class SubClass1 : BaseClass {}

class SubClass2 : SubClass1 {
public void BaseTest() {
base.DoSomething();
}
}

Listing 4.5 Methodenaufruf in der direkten Basisklasse

Ein umgeleiteter Aufruf an eine indirekte Basisklasse mit

// unzulässiger Aufruf

base.base.DoSomething();

ist nicht gestattet. Handelt es sich bei der über base aufgerufenen Methode um eine parametrisierte, müssen den Parametern die entsprechenden Argumente übergeben werden.

base ist eine implizite Referenz und als solche an eine konkrete Instanz gebunden. Das bedeutet konsequenterweise, dass dieses Schlüsselwort nicht zum Aufruf von statischen Methoden verwendet werden kann.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
  Zum Katalog
Zum Katalog: Visual C# 2012

Visual C# 2012
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: C++ Handbuch






 C++ Handbuch


Zum Katalog: C/C++






 C/C++


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2013
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de