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.4 Polymorphie Zur nächsten ÜberschriftZur vorigen Überschrift

In Abschnitt 4.2, »Der Problemfall geerbter Methoden«, haben Sie erfahren, dass die beiden Methoden Starten und Landen in der Klasse Luftfahrzeug unterschiedlich bereitgestellt werden können. Es ist nun an der Zeit, darauf einzugehen, welche weitergehenden Konsequenzen die drei verschiedenen Methodenimplementierungen in der Vererbungslinie haben.

Dazu schreiben wir in der Main-Methode zunächst Programmcode, mit dem abstrakt, virtuell und klassisch implementierte Methoden getestet werden sollen.


static void Main(string[] args) {
  Luftfahrzeug[] arr = new Luftfahrzeug[5];
  arr[0] = new Flugzeug();
  arr[1] = new Zeppelin();
  arr[2] = new Hubschrauber();
  arr[3] = new Hubschrauber();
  arr[4] = new Flugzeug();
  foreach(Luftfahrzeug temp in arr) {
    temp.Starten();           
  }
  Console.ReadLine();
}

Zuerst wird ein Array vom Typ Luftfahrzeug deklariert. Jedes Array-Element ist also vom Typ Luftfahrzeug. Weil die Klassen Flugzeug, Hubschrauber und Zeppelin von diesem Typ abgeleitet sind, kann jedem Array-Element auch die Referenz auf ein Objekt vom Typ der drei Subklassen zugewiesen werden:


arr[0] = new Flugzeug();
arr[1] = new Zeppelin();
arr[2] = new Hubschrauber();
...

Danach wird innerhalb einer foreach-Schleife auf alle Array-Elemente die Methode Starten aufgerufen. Die Laufvariable ist vom Typ Luftfahrzeug, also vom Typ der Basisklasse. In der Schleife wird auf diese Referenz die Starten-Methode aufgerufen.


Galileo Computing - Zum Seitenanfang

4.4.1 »Klassische« Methodenimplementierung Zur nächsten ÜberschriftZur vorigen Überschrift

Wir wollen an dieser Stelle zunächst die klassische Methodenimplementierung in der Basisklasse testen. Die drei Subklassen sollen die geerbte Methode Starten mit dem Modifizierer new überdecken. Stellvertretend sei an dieser Stelle nur der für uns wesentliche Ausschnitt aus der Klasse Flugzeug angegeben:


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

Starten wir die Anwendung, wird die Ausgabe im Konsolenfenster so lauten:


Das Luftfahrzeug startet.
Das Luftfahrzeug startet.
Das Luftfahrzeug startet.
Das Luftfahrzeug startet.
Das Luftfahrzeug startet.

Das Ergebnis ist zwar nicht spektakulär, hat aber weitreichende Konsequenzen. Wir müssen uns nämlich die Frage stellen, ob die Ausgabe das ist, was wir erreichen wollten. Vermutlich nicht, denn eigentlich sollte doch jeweils die klassenspezifische Methode Starten in der abgeleiteten Klasse ausgeführt werden.

Das ursächliche Problem ist das statische Binden des Methodenaufrufs an die Basisklasse. Statisches Binden heißt, dass die auszuführende Operation bereits zur Kompilierzeit festgelegt wird. Der Compiler stellt fest, von welchem Typ das Objekt ist, auf dem die Methode aufgerufen wird, und erzeugt den entsprechenden Code. Statisches Binden führt dazu, dass die Methode der Basisklasse aufgerufen wird, obwohl eigentlich die neue Methode in der abgeleiteten Klasse erforderlich wäre.

Das Beispiel macht deutlich, welchen Nebeneffekt das Überdecken einer Methode mit dem Modifizierer new haben kann: Der Compiler betrachtet das Objekt, als wäre es vom Typ der Basisklasse, und ruft die unter Umständen aus logischer Sicht sogar fehlerhafte Methode in der Basisklasse auf.


Galileo Computing - Zum Seitenanfang

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

Nun ändern wir den Programmcode in der Basisklasse Luftfahrzeug und stellen die Methode Starten als abstrakte Methode zur Verfügung. Die ableitenden Klassen erfüllen die Vertragsbedingung und überschreiben die geerbte Methode mit override. Am Testcode in Main nehmen wir keine Änderungen vor.


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

Ein anschließender Start der Anwendung bringt ein ganz anderes Ergebnis, als im ersten Versuch:


Das Flugzeug startet.
Der Zeppelin startet.
Der Hubschrauber startet.
Der Hubschrauber startet.
Das Flugzeug startet.

Tatsächlich werden nun die typspezifischen Methoden aufgerufen.

Anscheinend ist die Laufvariable temp in der Lage, zu entscheiden, welche Methode anzuwenden ist. Dieses Verhalten unterscheidet sich gravierend von dem, was wir im Zusammenhang mit den mit new ausgestatteten, überdeckenden Methoden zuvor gesehen haben. Die Bindung des Methodenaufrufs kann nicht statisch sein, sie erfolgt dynamisch zur Laufzeit.

Die Fähigkeit, auf einer Basisklassenreferenz die typspezifische Methode aufzurufen, wird als Polymorphie bezeichnet, und sie ist neben der Kapselung und der Vererbung die dritte Stütze der objektorientierten Programmierung.

Polymorphie bezeichnet ein Konzept der Objektorientierung, das besagt, dass Objekte bei gleichen Methodenaufrufen unterschiedlich reagieren können. Dabei können Objekte verschiedener Typen unter einem gemeinsamen Oberbegriff (d. h. einer gemeinsamen Basis) betrachtet werden. Die Polymorphie sorgt dafür, dass der Methodenaufruf automatisch bei der richtigen, also typspezifischen Methode landet.

Polymorphie arbeitet mit dynamischer Bindung. Der Aufrufcode wird nicht zur Kompilierzeit erzeugt, sondern erst zur Laufzeit der Anwendung, wenn die konkreten Typinformationen vorliegen. Im Gegensatz dazu legt die statische Bindung die auszuführende Operation wie gezeigt bereits zur Kompilierzeit fest.


Galileo Computing - Zum Seitenanfang

4.4.3 Virtuelle Methoden Zur nächsten ÜberschriftZur vorigen Überschrift


Hinweis

Den Programmcode zu dem folgenden Abschnitt finden Sie auf der Buch-DVD unter \Beispiele\Kapitel 4\Aircrafts.


Überschreibt eine Methode eine geerbte abstrakte Methode, zeigt die überschreibende Methode ausnahmslos immer polymorphes Verhalten. Wird in einer Basisklasse eine Methode »klassisch« implementiert und in der Subklasse durch eine Neuimplementierung mit new verdeckt, kann die verdeckende Methode niemals polymorph sein.

Vielleicht erahnen Sie an dieser Stelle schon, wozu virtuelle Methoden dienen. Erinnern wir uns: Eine Methode gilt als virtuelle Methode, wenn sie in der Basisklasse voll implementiert und mit dem Modifizierer virtual signiert ist. Damit 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.");
  }
}

Sie müssen eine virtuelle Methode als ein Angebot der Basisklasse an die Subklassen verstehen. Es ist das Angebot, die geerbte Methode entweder so zu erben, wie sie in der Basisklasse implementiert ist, sie bei Bedarf polymorph zu überschreiben oder eventuell auch einfach nur (nichtpolymorph) zu überdecken.

Polymorphes Überschreiben einer virtuellen Methode

Möchte die Subklasse die geerbte Methode neu implementieren und soll die Methode polymorphes Verhalten zeigen, muss die überschreibende Methode mit dem Modifizierer override signiert werden, z. B.:


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

Das Ergebnis des Aufrufs von Starten auf eine Basisklassenreferenz ist identisch mit dem Aufruf einer abstrakten Methode: Es wird die typspezifische Methode ausgeführt. An dieser Stelle lässt sich sofort schlussfolgern, dass der Modifizierer override grundsätzlich immer Polymorphie signalisiert.

Nichtpolymorphes Überdecken einer virtuellen Methode

Soll eine Subklasse eine geerbte virtuelle Methode nicht-polymorph überschreiben, kommt wiederum der Modifizierer new ins Spiel:


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

Die mit new neu implementierte virtuelle Methode zeigt kein polymorphes Verhalten, wenn wir die Testanwendung starten. Auch hier können wir unter Berücksichtigung des Verdeckens klassisch implementierter Methoden sagen, dass im Zusammenhang mit dem Modifizierer new niemals polymorphes Verhalten eintritt.

Weitergehende Betrachtungen

Es ist möglich, innerhalb einer Vererbungskette ein gemischtes Verhalten von Ausblendung und Überschreibung vorzusehen, wie das folgende Codefragment zeigt:


public class Luftfahrzeug {
  public virtual void Starten() { }
}
public class Flugzeug : Luftfahrzeug {
  public override void Starten () { }
}
public class Segelflugzeug : Flugzeug {
  public new void Starten() { }
}

Luftfahrzeug bietet die virtuelle Methode Starten an, und Flugzeug als Subklasse überschreibt diese mit override polymorph. Die nächste Ableitung in Segelflugzeug überdeckt Flugzeug jedoch nur noch mit new.

Wenn Sie nun nach der Zuweisung


Luftfahrzeug lfzg = new Segelflugzeug();

auf der Referenz lfzg die Methode Starten aufrufen, wird die Methode Starten in Flugzeug ausgeführt, da diese die aus Luftfahrzeug geerbte Methode polymorph überschreibt. Starten zeigt aber in der Klasse Segelflugzeug wegen des Modifikators new kein polymorphes Verhalten mehr.

Es ist hingegen nicht möglich, eine mit new überdeckende Methode durch override zu überschreiben, wie das folgende Codefragment zeigt:


// fehlerbehaftetes Überschreiben
public class Flugzeug : Luftfahrzeug {
  public new void Starten() { }
}
public class Segelflugzeug : Flugzeug {
  public override void Starten () { }
}

Ein einmal verloren gegangenes polymorphes Verhalten kann nicht mehr reaktiviert werden.

Zusammenfassende Anmerkungen

Um polymorphes Verhalten einer Methode zu ermöglichen, muss sie in der Basisklasse als virtual definiert sein. Virtuelle Methoden haben immer einen Anweisungsblock und stellen ein Angebot an die Subklassen dar: Entweder wird die Methode einfach nur geerbt, oder sie wird in der ableitenden Klasse neu implementiert. Zur Umsetzung des zuletzt angeführten Falls gibt es wiederum zwei Möglichkeiten:

  • Wird in der abgeleiteten Klasse die geerbte Methode mit dem Schlüsselwort override implementiert, wird die ursprüngliche Methode überschrieben – die abgeleitete Klasse akzeptiert das Angebot der Basisklasse. Ein Aufruf an eine Referenz der Basisklasse wird polymorph an den sich tatsächlich dahinter verbergenden Typ weitergeleitet.
  • In der abgeleiteten Klasse kann eine virtuelle Methode auch mit dem Modifizierer new ausgeblendet werden. Dann verdeckt die Methode in der Subklasse die geerbte Implementierung der Basisklasse und zeigt kein polymorphes Verhalten.

Eine statische Methode kann nicht virtuell sein. Ebenso ist eine Kombination des Schlüsselworts virtual mit abstract oder override nicht zulässig. Hinter der Definition einer virtuellen Methode verbirgt sich die Absicht, polymorphes Verhalten zu ermöglichen. Daher macht es auch keinen Sinn, ein privates Klassenmitglied als virtual zu deklarieren – es kommt zu einem Kompilierfehler. new und override dürfen nicht für dasselbe Member verwendet werden, sie schließen sich gegenseitig aus.


Tipp

Wenn Sie eine ableitbare Klasse entwickeln, sollten Sie grundsätzlich immer an die Subklassen denken. Polymorphie gehört zu den Fundamenten des objektorientierten Ansatzes. Methoden, die in abgeleiteten Klassen neu implementiert werden müssen, werden vermutlich immer polymorph überschrieben. Vergessen Sie daher die Angabe des Modifizierers virtual in keiner Methode – es sei denn, Sie haben handfeste Gründe dafür, polymorphe Aufrufe bereits im Ansatz zu unterbinden.


Die Methode »ToString()« der Klasse »Object« überschreiben

Die Klasse Object ist die Basis aller .NET-Typen und vererbt jeder Klasse eine Reihe elementarer Methoden. Dazu gehört auch ToString. Diese Methode ist als virtuelle Methode definiert und ermöglicht daher polymorphes Überschreiben. ToString liefert per Vorgabe den kompletten Typbezeichner des aktuellen Objekts als Zeichenfolge an den Aufrufer zurück, wird aber von vielen Klassen des .NET Frameworks überschrieben. Aufgerufen auf einen int, liefert ToString beispielsweise den von der int-Variablen beschriebenen Wert.

Wir wollen das Angebot der Methode ToString wahrnehmen und sie in der Klasse Circle ebenfalls polymorph überschreiben. Der Aufruf der Methode soll dem Aufrufer typspezifische Angaben liefern.


public class Circle {
  ...
  public override string ToString() {
    return "Circle, R=" + Radius + ",Fläche=" + GetArea();
  }
}


Galileo Computing - Zum Seitenanfang

4.4.4 Versiegelte Methoden Zur nächsten ÜberschriftZur vorigen Überschrift

Standardmäßig können alle Klassen abgeleitet werden. Ist dieses Verhalten für eine bestimmte Klasse nicht gewünscht, kann sie mit sealed versiegelt werden. Sie ist dann nicht weiter ableitbar. In ähnlicher Weise können Sie auch dem weiteren Überschreiben einer Methode einen Riegel vorschieben, indem Sie die Definition der Methode um den Modifizierer sealed ergänzen:


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

Eine von Flugzeug abgeleitete Klasse erbt zwar die versiegelte Methode Starten, kann sie aber selbst nicht mit override überschreiben. Es ist jedoch möglich, in einer weiter abgeleiteten Klasse eine geerbte, versiegelte Methode mit new zu überdecken, um eine typspezifische Anpassung vornehmen zu können.

Der Modifizierer sealed kann nur zusammen mit override in einer Methodensignatur einer abgeleiteten Klasse verwendet werden, wenn die Methode in der Basisklasse als virtuelle Methode bereitgestellt wird. Die Kombination sealed new ist unzulässig, ebenso das alleinige Verwenden von sealed in der Methodensignatur.


Galileo Computing - Zum Seitenanfang

4.4.5 Überladen einer Basisklassenmethode Zur nächsten ÜberschriftZur vorigen Überschrift

Oft ist es notwendig, die von einer Basisklasse geerbten Methoden in der Subklasse zu überladen, um ein Objekt vom Typ der Subklasse an speziellere Anforderungen anzupassen. Von einer Methodenüberladung wird bekanntlich dann gesprochen, wenn sich zwei gleichnamige Methoden einer Klasse nur durch ihre Parameterliste unterscheiden. Derselbe Begriff wird verwendet, wenn eine geerbte Methode in der Subklasse nach den Regeln der Methodenüberladung ergänzt werden muss.

Im folgenden Beispiel sehen Sie noch einmal die Klasse Flugzeug, die eine Methode namens Fliegen veröffentlicht:


public class Flugzeug {
  public virtual void Fliegen() {
    ..
  }
}

Die Klasse Segelflugzeug beerbt Flugzeug, implementiert allerdings eine von der geerbten Methode Fliegen abweichende Parameterliste und überlädt diese:


public class Segelflugzeug : Flugzeug {
  public void Fliegen(double distance) {
    ...
  }   
}

Wird mit


Segelflugzeug glider = new Segelflugzeug();

ein Objekt vom Typ der abgeleiteten Klasse erzeugt, kann auf dessen Referenz mit zwei Methoden operiert werden, z. B. so:


glider.Fliegen();
glider.Fliegen(300);


Galileo Computing - Zum Seitenanfang

4.4.6 Statische Member und Vererbung topZur vorigen Überschrift

Statische Member werden an die abgeleiteten Klassen vererbt. Eine statische Methode kann man auf die Klasse anwenden, in der die Methode definiert ist, oder auf die Angabe der abgeleiteten Klasse. Bezogen auf das Projekt GeometricObjects, können Sie demnach die statische Methode Bigger entweder mit


Circle.Bigger(kreis1, kreis2);

oder mit


GraphicCircle.Bigger(kreis1, kreis2);

aufrufen. Dabei sind kreis1 und kreis2 Objekte vom Typ Circle.

Unzulässig ist die Definition einer statischen Methode mit virtual, override oder abstract. Wollen Sie dennoch eine geerbte statische Methode in der abgeleiteten Klasse neu implementieren, können Sie die geerbte Methode mit einer Neuimplementierung verdecken, die den Modifizierer new aufweist.



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