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

Inhaltsverzeichnis
Vorwort zur 5. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Weitere .NET-Datentypen
7 Weitere Möglichkeiten von C#
8 Auflistungsklassen (Collections)
9 Fehlerbehandlung und Debugging
10 LINQ to Objects
11 Multithreading und die Task Parallel Library (TPL)
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 Einige wichtige .NET-Klassen
15 Projektmanagement und Visual Studio 2010
16 XML
17 WPF – Die Grundlagen
18 WPF-Containerelemente
19 WPF-Steuerelemente
20 Konzepte der WPF
21 Datenbindung
22 2D-Grafik
23 ADO.NET – verbindungsorientierte Objekte
24 ADO.NET – Das Command-Objekt
25 ADO.NET – Der SqlDataAdapter
26 ADO.NET – Daten im lokalen Speicher
27 ADO.NET – Aktualisieren der Datenbank
28 Stark typisierte DataSets
29 LINQ to SQL
30 Weitergabe von Anwendungen
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual C# 2010 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2010

Visual C# 2010
geb., mit DVD
1295 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1552-7
Pfeil 4 Vererbung, Polymorphie und Interfaces
Pfeil 4.1 Die Vererbung
Pfeil 4.1.1 Basisklassen und abgeleitete Klassen
Pfeil 4.1.2 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 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 Implizite Typumwandlung von Objektreferenzen
Pfeil 4.3.2 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 »Klassische« Methodenimplementierung
Pfeil 4.4.2 Abstrakte Methoden
Pfeil 4.4.3 Virtuelle Methoden
Pfeil 4.4.4 Versiegelte Methoden
Pfeil 4.4.5 Überladen einer Basisklassenmethode
Pfeil 4.4.6 Statische Member und Vererbung
Pfeil 4.5 Das Projekt »GeometricObjectsSolution« ergänzen
Pfeil 4.6 Hat-ein(e)-Beziehungen
Pfeil 4.6.1 Innere Klassen (Nested Classes)
Pfeil 4.7 Interfaces (Schnittstellen)
Pfeil 4.7.1 Schnittstellendefinition
Pfeil 4.7.2 Schnittstellenimplementierung
Pfeil 4.7.3 Interpretation der Schnittstellen
Pfeil 4.8 Das Zerstören von Objekten – der Garbage Collector
Pfeil 4.8.1 Arbeitsweise des Garbage Collectors
Pfeil 4.8.2 Expliziter Aufruf des Garbage Collectors
Pfeil 4.8.3 Der Destruktor
Pfeil 4.8.4 Die »IDisposable«-Schnittstelle
Pfeil 4.8.5 Ergänzungen in den Klassen »Circle« und »Rectangle«


Galileo Computing - Zum Seitenanfang

4.2 Der Problemfall geerbter Methoden Zur nächsten ÜberschriftZur vorigen Überschrift

Um das objektorientierte Konzept zu erläutern, habe ich mich bisher sehr häufig des Beispiels der beiden Klassen Circle und GraphicCircle bedient. Sie haben mit diesen beiden Klassen gelernt, wie die Struktur einer Klasse samt ihrer Felder, Methoden und Konstruktoren aufgebaut ist. Sie wissen nun auch, wie durch die Vererbung eine Klasse automatisch mit Fähigkeiten ausgestattet wird, die sie aus der Basisklasse erbt. Nun werden wir uns einer zweiten Klassenhierarchie zuwenden, um weitere Aspekte der Objektorientierung auf möglichst anschauliche Weise zu erklären.

Ausgangspunkt ist die Klasse Luftfahrzeug, die von den drei Klassen Flugzeug, Hubschrauber und Zeppelin beerbt wird. In der Klasse Luftfahrzeug sind die Felder definiert, die alle davon abgeleiteten Klassen gemeinsam aufweisen: Hersteller und Baujahr. Die Spannweite ist eine Eigenschaft, die nur ein Flugzeug hat, und sie ist daher in der Klasse Flugzeug implementiert. Ein Hubschrauber wiederum hat einen Rotordurchmesser, der Zeppelin ein Gasvolumen. Da alle drei abgeleiteten Typen sowohl starten als auch landen können, sind entsprechende Methoden in der Basisklasse Luftfahrzeug implementiert.

Das nachfolgende Codefragment bildet die Situation ab. Um den Code kurz, einfach und überschaubar zu halten, wird die Datenkapselung nicht berücksichtigt und werden die Eigenschaften als öffentliche Felder beschrieben. Zudem enthalten die beiden Methoden nur einen symbolischen Code.


public class Luftfahrzeug {
  public string Hersteller;
  public int Baujahr;
  public void Starten() {
    Console.WriteLine("Das Luftfahrzeug startet.");
  }
  public void Landen() {
    Console.WriteLine("Das Luftfahrzeug landet.");
  }
}
public class Flugzeug : Luftfahrzeug {
  public double Spannweite;
}
public class Hubschrauber : Luftfahrzeug {
  public double RotorDurchmesser;
}
public class Zeppelin : Luftfahrzeug {
  public double Gasvolumen;
}

In Abbildung 4.4 sehen Sie die Zusammenhänge auf anschauliche Art.

Abbildung 4.4 Die Hierarchie der Luftfahrzeuge

Grundsätzlich scheint die Vererbungshierarchie den Anforderungen zu genügen, aber denken Sie einen Schritt weiter: Ist die Implementierung der beiden Methoden Starten und Landen in der Basisklasse Luftfahrzeug richtig? Ein Flugzeug wird anders starten als ein Hubschrauber und ein Hubschrauber wiederum anders als ein Zeppelin. Ein Flugzeug benötigt eine Startbahn mit einer Mindestlänge, um überhaupt abheben zu können, während für einen Hubschrauber bereits eine freie Fläche in einer Größe genügt, die gewährleistet, dass die Rotorspitzen keine Hindernisse streifen. Um einen Zeppelin zu starten, wird eine Mannschaft benötigt, die das Halteseil löst und das Luftschiff zum Starten freigibt. Ähnliche Überlegungen können auch mit der Methode Landen angestellt werden.

Ein neues Problem offenbart sich plötzlich: Wie kann das Start- und Landeverhalten in der Basisklasse implementiert werden, wenn es für die Objekte der abgeleiteten Typen Zeppelin, Hubschrauber und Flugzeug unterschiedlich beschrieben werden muss? Mit der Lösung dieser Problematik, dass gleichnamige Methoden in verschiedenen Klassen unterschiedliche Implementierungen haben müssen, werden wir uns im Folgenden beschäftigen. Prinzipiell bieten sich drei Lösungsansätze an:

  • Wir verdecken die geerbten Methoden der Basisklasse in der Subklasse mit dem Modifizierer new.
  • Wir stellen in der Basisklasse abstrakte Methoden bereit, die von den erbenden Klassen überschrieben werden müssen.
  • Wir stellen in der Basisklasse virtuelle Methoden bereit.

Nachfolgend wollen wir alle drei Alternativen genau untersuchen.


Galileo Computing - Zum Seitenanfang

4.2.1 Geerbte Methoden mit »new« verdecken Zur nächsten ÜberschriftZur vorigen Überschrift

Nehmen wir an, dass in der Basisklasse die beiden Methoden Starten und Landen wie folgt codiert sind:


public class Luftfahrzeug {
  public void Starten() {
    Console.WriteLine("Das Luftfahrzeug startet.");
  }
  public void Landen() {
    Console.WriteLine("Das Luftfahrzeug landet.");
  }
}

Unsere drei abgeleiteten Klassen haben sehr wohl ein Interesse an den Methoden Starten und Landen – allerdings mit einer typspezifischen Implementierung. In der abgeleiteten Klasse müssen daher die geerbten Methoden der Basisklasse ausgeblendet (bzw. verdeckt) und neu codiert werden. Dabei hilft uns der Modifizierer new weiter. Exemplarisch sei das an der Klasse Flugzeug gezeigt, es gilt aber natürlich in gleicher Weise auch für Zeppelin und Hubschrauber:


public class Flugzeug : Luftfahrzeug {
  public new void Starten() {
    Console.WriteLine("Das Flugzeug startet.");
  }
  public new void Landen() {
    Console.WriteLine("Das Flugzeug landet.");
  }
}

Vom Verdecken oder Ausblenden einer geerbten Basisklassenmethode wird gesprochen, wenn in der abgeleiteten Klasse eine Methode implementiert wird,

  • die den gleichen Namen und
  • eine identische Parameterliste

besitzt wie eine Methode in der Basisklasse, diese aber durch eine eigene Implementierung vollständig ersetzt. Das ist beispielsweise dann der Fall, wenn die Implementierung in der Basisklasse für Objekte vom Typ der abgeleiteten Klasse falsch ist oder generell anders sein muss. Entscheidend für das Verdecken einer geerbten Methode ist, dass Sie die Methodendefinition in der Subklasse um den Modifizierer new ergänzen.

Wird eine Basisklassenmethode in der abgeleiteten Klasse verdeckt, wird beim Aufruf der Methode auf Objekten vom Typ der Subklasse immer die verdeckende Version ausgeführt. Die Anweisungen


Flugzeug flg = new Flugzeug();
flg.Starten();

führen im Befehlsfenster zu der Ausgabe: Das Flugzeug startet.


Hinweis

In gleicher Weise, wie eine geerbte Instanzmethode in einer ableitenden Klasse verdeckt werden kann, lassen sich mit new auch Eigenschaftsmethoden, Felder und statische Komponenten einer Basisklasse verdecken und durch eine typspezifische Implementierung ersetzen.


Die Sichtbarkeit eines verdeckenden Klassenmitglieds

Zugriffsmodifizierer beschreiben die Sichtbarkeit. Ein als public deklariertes Mitglied ist über die Grenzen der aktuellen Anwendung hinaus bekannt, während der Modifizierer internal die Sichtbarkeit auf die aktuelle Assemblierung beschränkt. private Klassenmitglieder hingegen sind nur in der definierenden Klasse sichtbar.

Ein verdeckendes Member muss nicht zwangsläufig denselben Zugriffsmodifizierer haben wie das in der Basisklasse. Machen wir uns das kurz an der Klasse Flugzeug klar, und verdecken wir die geerbte Methode Starten der Klasse Luftfahrzeug durch eine private-Implementierung in Flugzeug.


public class Flugzeug : Luftfahrzeug {
  ...
  private new void Starten() {
    Console.WriteLine("Das Flugzeug startet.");
  }
}

Die verdeckende Methode Starten ist nun nur innerhalb von Flugzeug sichtbar. Einen interessanten Effekt stellen wir fest, wenn wir jetzt den folgenden Code schreiben:


static void Main(string[] args) {
  Flugzeug flg = new Flugzeug();
  flg.Starten();
}

Im Konsolenfenster wird Das Luftfahrzeug startet. ausgegeben.

Aus allem, was bisher gesagt worden ist, müssen wir die Schlussfolgerung ziehen, dass das vollständige Ausblenden eines geerbten Mitglieds nicht möglich ist, auch nicht durch »Privatisierung«. Das führt uns zu folgender Erkenntnis:


Merksatz

Grundsätzlich werden alle Member der Basisklasse geerbt. Davon gibt es keine Ausnahme. Auch das Ausblenden durch Privatisierung in der erbenden Klasse ist nicht möglich.


Wollen Sie unter keinen Umständen eine Methode aus der Basisklasse erben, bleibt Ihnen nur ein Weg: Sie müssen das Konzept Ihrer Klassenhierarchie neu überdenken.


Galileo Computing - Zum Seitenanfang

4.2.2 Abstrakte Methoden Zur nächsten ÜberschriftZur vorigen Überschrift

Mit dem Modifizierer new können die aus der Basisklasse geerbten Methoden in der ableitenden Klasse überdeckt werden. Allerdings ist dieser Lösungsweg mit einem Nachteil behaftet, denn er garantiert nicht, dass alle ableitenden Klassen die geerbten Methoden Starten und Landen überdecken und durch eine typspezifische Implementierung ersetzen. Jede unserer abgeleiteten Klassen sollte aber hinsichtlich der Behandlung einer Basisklassenoperation gleichwertig sein. Wird die Neuimplementierung beispielsweise in der Klasse Hubschrauber vergessen, ist dieser Typ mit einem möglicherweise entscheidenden Fehler behaftet, weil er keine typspezifische Neuimplementierung hat.

Wie können wir aber alle Klassen, die von der Klasse Luftfahrzeug abgeleitet werden, dazu zwingen, die Methoden Starten und Landen neu zu implementieren? Gehen wir noch einen Schritt weiter, und stellen wir uns die Frage, ob wir überhaupt dann noch Code in den Methoden Starten und Landen der Klasse Luftfahrzeug benötigen. Anscheinend nicht. Dass wir die beiden Methoden in der Basisklasse definiert haben, liegt im Grunde genommen nur daran, dass wir diese Methoden in den ableitenden Klassen bereitstellen wollen.

Mit dieser Erkenntnis mag die Lösung der aufgezeigten Problematik im ersten Moment verblüffen: Tatsächlich werden Starten und Landen in der Basisklasse nicht implementiert – sie bleiben einfach ohne Programmcode. Damit wäre aber noch nicht sichergestellt, dass die Subklassen die geerbte »leere« Methode typspezifisch implementieren. Deshalb wird in solchen Fällen sogar auf den Anweisungsblock verzichtet, der durch die geschweiften Klammern beschrieben wird.

In der objektorientierten Programmierung werden Methoden, die keinen Anweisungsblock aufweisen, als abstrakte Methoden bezeichnet. Neben den Methoden, die das Verhalten eines Typs beschreiben, können auch Eigenschaften als abstrakt definiert werden.

Abstrakte Methoden werden durch die Angabe des abstract-Modifizierers in der Methodensignatur gekennzeichnet, am Beispiel unserer Methoden Starten und Landen also durch:


public abstract void Starten();
public abstract void Landen();

Abstrakte Methoden enthalten niemals Code. Die Definition einer abstrakten Methode wird mit einem Semikolon direkt hinter der Parameterliste abgeschlossen, die geschweiften Klammern des Anweisungsblocks entfallen.

Welchen Stellenwert nimmt aber eine Klasse ein, die eine Methode veröffentlicht, die keinerlei Verhalten aufweist? Die Antwort ist verblüffend einfach: Eine solche Klasse kann nicht instanziiert werden – sie rechtfertigt ihre Existenz einzig und allein dadurch, den Subklassen als Methodengeber zu dienen. Damit wird das Prinzip der objektorientierten Programmierung, gemeinsame Verhaltensweisen auf eine höhere Ebene auszulagern, nahezu auf die Spitze getrieben.

Eine nicht instanziierbare Klasse, die mindestens ein durch abstract gekennzeichnetes Member enthält, ist ihrerseits selbst abstrakt und wird deshalb als abstrakte Klasse bezeichnet. Abstrakte Klassen machen nur dann Sinn, wenn von ihnen weitere Klassen abgeleitet werden. Syntaktisch wird dieses Verhalten in C# durch die Ergänzung des Modifikators abstract in der Klassensignatur beschrieben:


public abstract class Luftfahrzeug {
  public abstract void Starten();
  public abstract void Starten();
  ...
}

Neben abstrakten Methoden darf eine abstrakte Klasse auch vollständig implementierte Methoden und Eigenschaften bereitstellen. So könnte die Klasse Luftfahrzeug beispielsweise eine Methode Fliegen enthalten, wie das folgende Codefragment zeigt:


public abstract class Luftfahrzeug {
  private string hersteller;
  // abstrakte Methoden
  public abstract void Starten();
  public abstract void Landen();
  // konkrete Methode
  public void Fliegen() {
    ...
  }
}

Die Signatur einer Methode und infolgedessen auch der dazugehörigen Klasse mit dem Modifizierer abstract kommt einer Forderung gleich: Alle nicht abstrakten Ableitungen einer abstrakten Klasse müssen die abstrakten Methoden der Basisklasse überschreiben.

Wird in einer abgeleiteten Klasse das abstrakte Mitglied der Basisklasse nicht überschrieben, muss die abgeleitete Klasse in jedem Fall ebenfalls als abstract gekennzeichnet werden. Als Konsequenz dieser Aussagen bilden abstrakte Klassen das Gegenkonstrukt zu den Klassen, die mit sealed als nicht ableitbar gekennzeichnet sind. Daraus folgt auch, dass die Modifizierer sealed und abstract nicht nebeneinander verwendet werden dürfen.


Hinweis

Eine Klasse, die eine als abstrakt definierte Methode enthält, muss ihrerseits selbst abstrakt sein. Der Umkehrschluss ist allerdings nicht richtig, denn eine abstrakte Klasse ist nicht zwangsläufig dadurch gekennzeichnet, ein abstraktes Mitglied zu enthalten. Eine Klasse kann auch dann abstrakt sein, wenn keines ihrer Member abstrakt ist. Auf diese Weise wird eine Klasse nicht instanziierbar, und das Ableiten von dieser Klasse wird erzwungen.


abstract kann nur im Zusammenhang mit Instanzmembern benutzt werden. Statische Methoden können nicht abstrakt sein, deshalb ist das gleichzeitige Auftreten von static und abstract in einer Methodensignatur unzulässig.

Abstrakte Methoden überschreiben

Das folgende Codefragment beschreibt die Klasse Hubschrauber. In der Klassenimplementierung werden die abstrakten Methoden Starten und Landen der Basisklasse überschrieben. Um zu kennzeichnen, dass eine abstrakte Basisklassenmethode überschrieben wird, verwenden Sie den Modifizierer override:


class Hubschrauber : Luftfahrzeug {
  public override void Starten() { 
    Console.WriteLine("Der Hubschrauber startet.");
  }
  public override void Landen() { 
    Console.WriteLine("Der Hubschrauber landet.");
  }
}

Sollten Sie dieses Beispiel ausprobieren, müssen Sie Starten und Landen selbstverständlich auch in den Klassen Flugzeug und Zeppelin mit override überschreiben.


Galileo Computing - Zum Seitenanfang

4.2.3 Virtuelle Methoden topZur vorigen Überschrift

Widmen wir uns nun der dritten anfangs aufgezeigten Variante, den virtuellen Methoden. Unser Ausgangspunkt sei dabei folgender: Wir wollen Starten und Landen wieder in der Basisklasse vollständig implementieren. Damit wären wir wieder am Ausgangspunkt angelangt – mit einem kleinen Unterschied: Wir ergänzen die Methoden Starten und Landen mit dem Modifizierer virtual. Dann sieht die Klasse Luftfahrzeug wie folgt aus:


public class Luftfahrzeug {
  public virtual void Starten() {
    Console.WriteLine("Das Luftfahrzeug startet.");
  }
  public virtual void Landen() {
    Console.WriteLine("Das Luftfahrzeug landet.");
  }
}

Nun sind die beiden Methoden als virtuelle Methoden in der Basisklasse definiert. Eine Subklasse hat nun die Wahl zwischen drei Alternativen:

  • Die Subklasse erbt die Methoden, ohne eine eigene, typspezifische Implementierung vorzusehen, also:

public class Flugzeug : Luftfahrzeug { }

  • Die Subklasse verdeckt die geerbten Methoden mit new, hier also:

public class Flugzeug : Luftfahrzeug {
  public new void Starten() {
    Console.WriteLine("Das Flugzeug startet.");
  }
  public new void Landen() {
    Console.WriteLine("Das Flugzeug landet.");
  }
}

  • Die Subklasse überschreibt die geerbten Methoden mit override, also:

public class Flugzeug : Luftfahrzeug {
  public override void Starten() {
    Console.WriteLine("Das Flugzeug startet.");
  }
  public override void Landen() {
    Console.WriteLine("Das Flugzeug landet.");
  }
}

Sie werden sich an dieser Stelle wahrscheinlich fragen, worin sich die beiden letztgenannten Varianten unterscheiden. Diese Überlegung führt uns nach der Datenkapselung und der Vererbung zum dritten elementaren Konzept der Objektorientierung: zur Polymorphie. Ehe wir uns aber mit der Polymorphie beschäftigen, müssen Sie vorher noch die Typumwandlung in einer Vererbungshierarchie verstehen.



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# 2010

Visual C# 2010
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 2010
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.


Nutzungsbestimmungen | Datenschutz | Impressum

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

Cookie-Einstellungen ändern