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

Inhaltsverzeichnis
1 Einführung
2 Grundlagen der Sprachsyntax
3 Klassendesign
4 Weitere Datentypen
5 Multithreading
6 Collections und LINQ
7 Eingabe und Ausgabe
8 Anwendungen: Struktur und Installation
9 Code erstellen und debuggen
10 Einige Basisklassen
11 Windows-Anwendungen erstellen
12 Die wichtigsten Steuerelemente
13 Tastatur- und Mausereignisse
14 MDI-Anwendungen
15 Grafiken mit GDI+
16 Drucken
17 Entwickeln von Steuerelementen
18 Programmiertechniken
19 WPF – Grundlagen
20 Layoutcontainer
21 WPF-Steuerelemente
22 Konzepte von WPF
23 Datenbankverbindung mit ADO.NET
24 Datenbankabfragen mit ADO.NET
25 DataAdapter
26 Offline mit DataSet
27 Datenbanken aktualisieren
28 Stark typisierte DataSets
A Anhang: Einige Übersichten
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual Basic 2008 von Andreas Kuehnel, Stephan Leibbrandt
Das umfassende Handbuch
Buch: Visual Basic 2008

Visual Basic 2008
3., aktualisierte und erweiterte Auflage, geb., mit DVD
1.323 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1171-0
Pfeil 3 Klassendesign
Pfeil 3.1 Objektorientierung
Pfeil 3.1.1 Einführung
Pfeil 3.1.2 Vorteile
Pfeil 3.1.3 Klassenimplementierung in .NET
Pfeil 3.1.4 Klassen in Visual Basic
Pfeil 3.1.5 Projekttyp »Klassenbibliothek«
Pfeil 3.1.6 Bemerkung zu den Codefragmenten
Pfeil 3.1.7 Objekte durch New
Pfeil 3.1.8 Ausnahmen mit Throw auslösen
Pfeil 3.1.9 Datentypen
Pfeil 3.1.10 Sichtbarkeit der Klasse
Pfeil 3.1.11 Aufteilung der Definition mit Partial
Pfeil 3.1.12 Grafikbibliothek: Beispiel für Kapitel 3 und 4
Pfeil 3.2 Kapselung
Pfeil 3.2.1 Kombinationen
Pfeil 3.2.2 Private
Pfeil 3.2.3 Sichtbarkeitsmodifikatoren
Pfeil 3.2.4 Lokale Variablen
Pfeil 3.2.5 Softwareschutz
Pfeil 3.2.6 Grafikbibliothek: private Größe des Rechtecks
Pfeil 3.3 Verhalten (Methoden)
Pfeil 3.3.1 Prinzip
Pfeil 3.3.2 Verlassen der Methode
Pfeil 3.3.3 Parameter
Pfeil 3.3.4 Überladung (Overloads)
Pfeil 3.3.5 Rückgabewert
Pfeil 3.3.6 Reine Deklaration mit Partial
Pfeil 3.3.7 Grafikbibliothek: Zugriffsmethoden auf die Größe des Rechtecks
Pfeil 3.4 Bindung
Pfeil 3.4.1 Klassenbindung mit Shared
Pfeil 3.4.2 Klassenkonstruktoren
Pfeil 3.4.3 Externe Funktionen
Pfeil 3.4.4 Grafikbibliothek: Rechteckübergreifendes
Pfeil 3.5 Objektinitialisierung mit Konstruktoren
Pfeil 3.5.1 Objektkonstruktoren
Pfeil 3.5.2 Nichtöffentliche Konstrukturen
Pfeil 3.5.3 Grafikbibliothek: Initialisierung des Rechtecks
Pfeil 3.6 Zustände (Felder)
Pfeil 3.6.1 Deklaration und Initialisierung
Pfeil 3.6.2 Sichtbarkeit
Pfeil 3.6.3 Konstanten: ReadOnly und Const
Pfeil 3.6.4 With
Pfeil 3.6.5 Grafikbibliothek: Konstanten des Rechtecks
Pfeil 3.7 Eigenschaften
Pfeil 3.7.1 Kontrolle beim Aufrufer
Pfeil 3.7.2 Zugriffsmethoden
Pfeil 3.7.3 Getter und Setter: Property
Pfeil 3.7.4 Indexer
Pfeil 3.7.5 Standardeigenschaft: Default
Pfeil 3.7.6 Schreibschutz und Leseschutz: ReadOnly und WriteOnly
Pfeil 3.7.7 Sichtbarkeit
Pfeil 3.7.8 Klammern
Pfeil 3.7.9 Grafikbibliothek: Eigenschaften des Rechtecks
Pfeil 3.8 Innere Klassen
Pfeil 3.8.1 Beziehung zur äußeren Klasse
Pfeil 3.8.2 Sichtbarkeit
Pfeil 3.8.3 Grafikbibliothek: Position des Rechtecks
Pfeil 3.9 Dynamisches Verhalten: Delegate und Function
Pfeil 3.9.1 Funktionszeiger: Delegates
Pfeil 3.9.2 Automatisch generierter Code
Pfeil 3.9.3 Mehrere Aktionen gleichzeitig
Pfeil 3.9.4 Asynchrone Aufrufe
Pfeil 3.9.5 Funktionsobjekte: Function (λ-Ausdrücke)
Pfeil 3.9.6 Umwandlungen
Pfeil 3.9.7 Grafikbibliothek: Vergleich von Rechtecken
Pfeil 3.10 Ereignisse
Pfeil 3.10.1 Ereignis: Event und RaiseEvent
Pfeil 3.10.2 Statische Methodenbindung WithEvents und Handles
Pfeil 3.10.3 Dynamische Methodenbindung: AddHandler und RemoveHandler
Pfeil 3.10.4 Benutzerdefinierte Ereignisse: Custom Event
Pfeil 3.10.5 Umwandlungen
Pfeil 3.10.6 Grafikbibliothek: Größenänderungen von Rechtecken überwachen
Pfeil 3.11 Benutzerdefinierte Operatoren
Pfeil 3.11.1 Prinzip
Pfeil 3.11.2 Überladung
Pfeil 3.11.3 Vergleich
Pfeil 3.11.4 Typumwandlung mit CType: Widening und Narrowing
Pfeil 3.11.5 Wahrheitswerte: IsTrue und IsFalse
Pfeil 3.11.6 Grafikbibliothek: Addition und Umwandlung von Rechtecken
Pfeil 3.12 Alle Klassenelemente
Pfeil 3.12.1 Der Namensraum My
Pfeil 3.13 Vererbung
Pfeil 3.13.1 Klassenbeziehung durch Inherits
Pfeil 3.13.2 Sichtbarkeitsmodifikatoren
Pfeil 3.13.3 Zugriff auf Eltern mit MyBase
Pfeil 3.13.4 Modifikation: Shadows und Overloads (Overrides)
Pfeil 3.13.5 Abstrakte Klassen: MustInherit und MustOverride
Pfeil 3.13.6 Spezifische Catch-Blöcke
Pfeil 3.13.7 Eigene Ausnahmen
Pfeil 3.13.8 Arrays
Pfeil 3.13.9 Grafikbibliothek: neue Vielecke
Pfeil 3.14 Polymorphie
Pfeil 3.14.1 Virtuelle Methoden: Overridable und Overrides
Pfeil 3.14.2 Unterbrechen: Overloads, Shadows und Overrides
Pfeil 3.14.3 Unterbinden: NotInheritable, NotOverridable und MyClass
Pfeil 3.14.4 Konstruktoren
Pfeil 3.14.5 Equals
Pfeil 3.14.6 ToString
Pfeil 3.14.7 Grafikbibliothek: Objektsammlungen
Pfeil 3.15 Schnittstellen: Interface und Implements
Pfeil 3.15.1 Benennungen und Parameter
Pfeil 3.15.2 Schnittstellen und Vererbung
Pfeil 3.15.3 Schnittstellenvererbung
Pfeil 3.15.4 Schnittstelle oder abstrakte Klasse?
Pfeil 3.15.5 Grafikbibliothek: Flächen
Pfeil 3.16 Lebensende eines Objekts
Pfeil 3.16.1 Garbage Collector
Pfeil 3.16.2 Destruktoren
Pfeil 3.16.3 Dispose und Using


Rheinwerk Computing - Zum Seitenanfang

3.13 Vererbung Zur nächsten ÜberschriftZur vorigen Überschrift

In diesem Abschnitt betreten wir endlich die Bühne der Vererbung. Sie werden schon sehr bald feststellen, dass sich damit eine neue Welt der Programmierung öffnet. Trotzdem bleibt die Anzahl der Konzepte überschaubar. Es reicht, die folgenden zehn Begriffe in Tabelle 3.22 neu hinzuzunehmen. Die Erklärung, was sie bedeuten, wird fast den ganzen Rest des Kapitels füllen.


Tabelle 3.22 Schlüsselwörter im Rahmen der Vererbung

Art Schlüsselwörter

Typbeziehung

Implements und Inherits

Redefinition

Shadows, Overridable und Overrides

Erzwingung

MustInherit und MustOverride

Abschluss

NotInheritable und NotOverridable

Zugriff

MyBase


Der Grund dafür, Vererbung in der Programmierung einzuführen, ist im Grunde der gleiche wie im realen Leben: Eine neue Generation muss nicht wieder von vorn anfangen, sondern kann auf dem aufbauen, was ihr die vorherige Generation hinterlassen hat. Von außen ist nicht feststellbar, ob ein Vermögen selbst erarbeitet oder geerbt wurde. Genauso ist es in der Programmierung. Für die Verwendung eines Objekts ist es irrelevant, ob ein Wert oder eine Funktionalität direkt von dem Objekt oder mittels Vererbung zur Verfügung gestellt wird. Dies schafft eine Entkopplung der Funktionalität von dem, wie sie konkret programmiert ist. Außerdem beschränkt die Vererbung in keiner Weise die Schaffung neuer Funktionalität.


Vererbung ist eine reine Erweiterung der bisherigen Konzepte.


Bei der Vererbung ist zu beachten, dass nicht die Objekte selbst an der Vererbung teilnehmen, sondern die Vorlagen für Objekte, die Klassen. Wenn eine Klasse durch Vererbung mehr kann, hat natürlich auch das Objekt, das durch die Klasse mittels des New-Operators erzeugt wird, die größere Funktionalität. Es werden aber keine konkreten Daten weitergegeben. Was vererbt wird, ist die Tatsache, dass ein gewisser Satz an Daten im Objekt vorhanden ist. Genauso wenig wie ein Lehrer sein Wissen durch dessen Weitergabe verliert, »verbraucht« sich eine Klasse, wenn sie beerbt wird. Dies ermöglicht es vielen Klassen, eine einzelne Klasse zu beerben: Ein und dieselbe Klasse kann die Elternklasse vieler Klassen sein.

Wie im realen Leben wird durch die Vererbung auch eine Nachfolge geschaffen. Tritt jemand ein Universalerbe an, übernimmt er alle Rechte und Pflichten des Erblassers. Hatte dieser zum Beispiel Schulden, können sich die Gläubiger an den Erben wenden, als sei er der Schuldner selbst. Der Erbe ist dann ein Stellvertreter für den Beerbten. Genau dasselbe passiert auch in der Vererbung in der Softwaretechnik. Eine Kindklasse kann auch verwendet werden, als sei sie die Elternklasse. Das Konzept eines Teilerbes gibt es in Visual Basic nicht. Die Devise heißt »Entweder alles oder nichts« , weil nur dann das Konzept des Stellvertreters ausnahmslos funktioniert. Dieses Verhalten lässt sich wie folgt formulieren:


Vererbung etabliert eine Ist-eine–Beziehung.


Damit einher geht automatisch die Tatsache, dass Vererbung transitiv ist. Zum Beispiel ist eine Ellipse ein geometrisches Objekt, und ein Kreis ist ein Spezialfall einer Ellipse. Über diese Beziehungskette ist dann, ohne explizite Spezifikation, ein Kreis ein geometrisches Objekt. Damit ist es ausreichend, den direkten Vorfahren zu kennen. Die in der Hierarchie weiter oben stehenden Ahnen sind damit implizit (automatisch) auch bekannt.


Vererbung ist transitiv.


Die Komplettübernahme von den Eltern lässt sich abmildern. Ein Kind ist oft unzufrieden mit den Eltern und möchte dann nie so werden wie sie. Glücklicherweise kann jeder seine eigenen Verhaltensweisen entwickeln und so Übernommenes variieren oder sogar komplett ersetzen. Genauso geht das in Visual Basic. Eine Kindklasse kann die Form übernehmen und sie mit neuem Inhalt füllen. Dabei darf sie auf die Definition der Elternklasse zurückgreifen. Im realen Leben wird ein Kind ja auch selten eine Verhaltensweise der Eltern gänzlich verdammen, sondern es wird wahrscheinlicher den »guten Kern« beibehalten wollen.

Bis jetzt sind wir noch gar nicht auf Schwierigkeiten eingegangen, die sich dadurch ergeben können, dass es mehr als einen Elternteil gibt. Angenommen, Sie übernehmen von Ihrer Mutter und Ihrem Vater ein Rezept für den »gleichen« Kuchen. Wenn Sie nun gebeten werden »diesen« Kuchen zu backen, stellt sich das Problem, welches der Rezepte Sie nehmen. Die Angabe »dieser« Kuchen ist zweideutig.

Im Gegensatz dazu haben Sie, wie Ihre Eltern, einen Namen. Es wäre nun unsinnig, von jedem Elternteil den Namen zu übernehmen, da sonst in der dritten Generation bereits acht Namen nötig wären, und seit der Gründung der ersten Städte wäre die Liste so lang geworden, dass die Zahl der Atome im Universum nicht ausreicht, um sie zu speichern. In dieser Situation möchten Sie also die Eigenschaft »Name« nur einmal von Ihren Eltern übernehmen. Das Beziehungsgeflecht wird damit schnell derart unübersichtlich, dass die Vorteile einer Mehrfachvererbung in den Hintergrund treten. Demgegenüber führt die Beschränkung auf einen Elternteil auf eine einfache Struktur:


Einfachvererbung lässt sich als Baum darstellen, der eine einzige Wurzel hat.


Was ist die Konsequenz? Eine Beschränkung ausschließlich auf Einfachvererbung wäre ein recht drastischer Schritt. Um ihn abzumildern, wählt Visual Basic einen Mittelweg zwischen Einfach- und Mehrfachvererbung. Was zusätzlich zur Einfachvererbung erlaubt ist, wird durch eine Analyse der Vererbung verständlich. Sie besteht aus zwei Parteien:

  • dem Nutzer: Der Nutzer muss wissen, in welcher Form das Geerbte angesprochen wird.
  • dem Erblasser: Der Erblasser definiert die Form.

Auch ohne konkrete Ausgestaltung der Funktionalität ist es bereits möglich, das Geerbte formell korrekt anzusprechen. In der Terminologie von Visual Basic bedeutet dies die Festlegung der Signatur, zum Beispiel einer Methode. Damit lässt sich bereits der gesamte Quelltext fertigstellen, der sie aufruft. Da nichts inhaltlich festgelegt wird, kann es nicht zu den Problemen kommen, die sich durch Mehrfachvererbung ergeben. Das geht so weit, dass dieselbe Form von verschiedenen Seiten gleichzeitig kommen kann, da sich an der Nutzung nichts ändert und inhaltlich ja noch nichts festgelegt worden ist. Erst beim Erben selbst wird konkretisiert, was formell durch die Eltern festgelegt wurde. Zum Beispiel kann sich ein Fahrzeug fortbewegen. Ein Auto und ein Boot sind Beispiele für Fahrzeuge. Ein Amphibienfahrzeug ist sowohl ein Auto als auch ein Boot. Würde nun bereits beim Auto oder Boot festgelegt, was Fortbewegung bedeutet, wäre ein solches Sonderfahrzeug nicht möglich. Legt man dagegen nur formell fest, dass sich ein Fahrzeug bewegen lässt, bleibt es jeweils dem Auto, dem Boot und dem Amphibienfahrzeug vorbehalten, die konkrete Fortbewegungsart selbst festzulegen. Datentypen, die nur eine Aussage über die Signaturen machen, werden in Visual Basic Schnittstellen genannt.


Schnittstellen etablieren eine Verhält-sich-wie-Beziehung.


Damit können wir die Vererbung in Visual Basic grob wie folgt charakterisieren:

  • Spezialisierung: Allgemeines wird durch Vererbung übernommen und dort spezialisiert, wo es nötig ist.
  • Verallgemeinerung: Gemeinsame Funktionalität kann in einer Elternklasse zusammengefasst werden.
  • Modularisierung: Vererbung erlaubt die Aufteilung eines großen Funktionsumfangs in kleine, handhabbare Einheiten, die mittels Vererbung aneinandergekoppelt werden.
  • Wiederverwendung: Da eine Klasse mehrfach beerbt werden kann, kann sie als Baustein für viele neue Klassen dienen, ohne dass man neu programmieren muss.
  • Stellvertreter: Eine Kindklasse übernimmt die Möglichkeiten einer Elternklasse komplett und kann dadurch genauso verwendet werden wie diese.
  • Einfachvererbung: Jede Klasse, außer Object als Wurzel aller Klassen, hat genau eine Elternklasse.
  • Mehrfache Schnittstellenvererbung: Jede Klasse kann beliebig viele Schnittstellen konkretisieren.
  • Explizite Vererbung findet nur zwischen Klassen und Schnittstellen statt, alle Werttypen und Module sind ausgeschlossen (Werttypen erben implizit von ValueType).

Bei den ganzen Möglichkeiten der Vererbung sollten Sie jedoch darauf achten, diese nur dort einzusetzen, wo sie angebracht ist. Sie sollten genau prüfen, ob wirklich ein Objekt ein anderes komplett repräsentieren kann (Ist-ein-Beziehung) oder ob eines das andere enthält (Hat-ein-Beziehung). Zum Beispiel ist ein Auto ein Fahrzeug, und es hat einen Motor. Kann ein Objekt ein anderes nur teilweise ersetzen, sollte in der Regel keine Vererbung eingesetzt werden.

Ist eine Vererbungsbeziehung sinnvoll, sollte bei der Deklaration von Variablen immer ein Typ in der Vererbungshierarchie gewählt werden, der gerade ausreicht, um die von der Variablen angesprochenen Klassenmitglieder anzusprechen. Das hat den Vorteil, dass sich Änderungen in den Klassen, die weiter unten in der Vererbungshierarchie liegen, nicht auf den Code auswirken. Dies macht Sie freier in der zukünftigen Anpassung Ihrer Klassen an neue Bedingungen. Zusammengefasst:


Variablentypen sollten so allgemein wie möglich und so speziell wie nötig sein.



Rheinwerk Computing - Zum Seitenanfang

3.13.1 Klassenbeziehung durch Inherits Zur nächsten ÜberschriftZur vorigen Überschrift

Eine Klasse erbt in Visual Basic durch eine einfache Deklaration der zu beerbenden Klasse hinter dem Schlüsselwort Inherits (deutsch: erben). Es darf nur eine Klasse angegeben werden, und die Deklaration folgt direkt auf die einleitende Class-Deklaration. Durch diese Deklaration übernimmt die erbende Klasse alle Funktionalität der Basis.


Class Basis 
  ... 
End Class 
Class Abgeleitet 
  Inherits Basis 
  ... 
End Class


Hinweis
Basis darf weder direkt noch indirekt ValueType, Enum, Array, MulticastDelegate oder Delegate sein, oder Attribute bei genrischen Klassen (alle im Namensraum System).


Die einfachste Form der Vererbung ist das Beerben, ohne selbst etwas hinzuzufügen. Das folgende Beispiel zeigt, wie sich die Klasse Champagner auf die Klasse Schaumwein stützt.


'...\Klassendesign\Vererbung\Prinzip.vb

Option Strict On 
Namespace Klassendesign

  Class Schaumwein 
    Friend perlt As Boolean = True 
  End Class

  Class Champagner 
    Inherits Schaumwein 
  End Class

  Module Prinzip 
    Sub Test() 
      Dim wein As Schaumwein = New Champagner() 
      Console.WriteLine("Der Wein perlt {0}", If(wein.perlt, "", "nicht")) 
      Console.WriteLine("Der Wein ist ein {0}", wein.GetType().Name) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Damit ist eine Beziehung zwischen den Klassen hergestellt: Jeder Champagner ist ein Schaumwein (aber nicht umgekehrt, wovon Sie sich selbst durch eigene Geschmacksexperimente überzeugen können). Dadurch kann er in einer Variablen vom Typ der Basisklasse gespeichert werden. Durch das Erbe kann der Champagner eine Aussage darüber machen, ob er perlt, da er ja alle Funktionalität des Schaumweins übernimmt. Wie die zweite Zeile der folgenden Ausgabe außerdem zeigt, hat der Wein seine Herkunft nicht vergessen, auch wenn er in der Deklaration Dim wein As Schaumwein als allgemeiner Schaumwein abgelegt wird.

Der Wein perlt 
Der Wein ist ein Champagner

Damit werden zwei ganz wesentliche Punkte deutlich:

  • Ein Objekt kann als Basistyp auftreten.
  • Der Typ des Objekts bleibt auch in einer Referenz auf den Basistyp erhalten.

Das ist erst der Anfang der Geschichte. Der nächste Schritt besteht in der Ergänzung beziehungsweise Änderung des Geerbten. Dazu ist nichts Besonderes nötig. Bis auf die Inherits-Klausel ist die Kindklasse eine ganz »normale« Klasse. Das nächste Codefragment zeigt eine Klasse Expedition, die von der Klasse Reise die Mitglieder Ziel und Dauer erbt. Zusätzlich ersetzt sie die Funktion Dauer und definiert ein neues Feld Leitung.


'...\Klassendesign\Vererbung\Ergaenzung.vb

Option Strict On 
Namespace Klassendesign

  Class Reise 
    Friend Ziel As String = "Nordwestpassage" 
    Friend Function Dauer() As String 
      Return "ewig" 
    End Function 
  End Class

  Class Expedition 
    Inherits Reise 
    Friend Leitung As String = "Arved Fuchs"      'Ergänzung 
    Friend Function Dauer() As String             'Ersatz 
      Return "wenige Monate" 
    End Function 
  End Class

  Module Ergänzung 
    Sub Test() 
      Dim expedition As Expedition = New Expedition() 
      Dim reise As Reise = expedition 
      Console.WriteLine("Ziel der Reise: {0}", reise.Ziel) 
      Console.WriteLine("Ziel der Expedition: {0}", expedition.Ziel) 
      Console.WriteLine("Leitung der Expedition: {0}", expedition.Leitung) 
      Console.WriteLine("Die Reise dauert {0}", reise.Dauer()) 
      Console.WriteLine("Die Expedition dauert {0}", expedition.Dauer()) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt, wie auf das geerbte Ziel und die neue Leitung zugegriffen wird. Die beiden letzten Zeilen machen deutlich, dass auf diejenige Definition zugegriffen wird, die dem Objekt am nächsten ist, das auf das Klassenmitglied zugreift.

Ziel der Reise: Nordwestpassage 
Ziel der Expedition: Nordwestpassage 
Leitung der Expedition: Arved Fuchs 
Die Reise dauert ewig 
Die Expedition dauert wenige Monate

Object

Alle Klassen basieren direkt oder indirekt auf der Klasse Object und erben von ihr einige Klassenmitglieder. Auf die öffentlichen Mitglieder haben alle Objekte Zugriff, und die mit Protected gekennzeichneten Mitglieder können in beliebigen eigenen Klassen verwendet oder neu definiert werden. Alle anderen sind nur mit Klassen aus dem Namensraum System.Reflection zugänglich. Eine Verwendung in eigenen Klassen sollte nie nötig sein. Sie sind nur der Vollständigkeit halber aufgelistet. Um die Auflistung kurz zu halten, wurde auf den Modifikator ByVal der Parameter verzichtet.

Public 
  Function GetType() As Type 
  Overridable Function GetHashCode() As Integer 
  Overridable Function ToString() As String 
  Overridable Function Equals(obj As Object) As Boolean 
  Shared Function Equals(a As Object, b As Object) As Boolean 
  Shared Function ReferenceEquals(a As Object, b As Object) As Boolean 
Protected 
  Function MemberwiseClone() As Object 
  Overridable Sub Finalize() 
Friend 
  Shared Function InternalEquals(a As Object, b As Object) As Boolean 
  Shared Function InternalGetHashCode(obj As Object) As Integer 
Private 
  Sub FieldSetter(type As String, fieldName As String, val As Object) 
  Sub FieldGetter(type As String, fieldName As String, ByRef val As Object) 
  Function GetFieldInfo(type As String, fieldName As String) As FieldInfo

Partial

Liegt eine Klassendefinition mit Partial in mehreren Teilen vor, muss nicht jeder Teil die Inherits-Klausel spezifizieren. Das folgende Codefragment ist daher gültig, aber es ist nicht sofort klar, ob perlt direkt in der Klasse oder auch in einer Elternklasse zu suchen ist.


'...\Klassendesign\Vererbung\Partial.vb

Option Strict On 
Namespace Klassendesign

  Partial Class Champagner 
    Private eigenschaften() As Object = {Me.perlt, "teuer"} 
  End Class

End Namespace

Hinweis
Um den Code besser lesbar zu halten, sollte eine Vererbungsbeziehung in jedem Teil einer partiellen Klasse angegeben werden (auch bei abstrakten und gesperrten Klassen sowie Schnittstellen – siehe Abschnitt 3.13.5, »Abstrakte Klassen: MustInherit und MustOverride«, Abschnitt 3.14.3, »Unterbinden: NotInheritable, NotOverridable und MyClass« sowie Abschnitt 3.15, »Schnittstellen: Interface und Implements«).


Typprüfung mit TypOf … Is

Die Ist-eine-Beziehung kann im Quelltext mit dem Operator TypeOf ... Is geprüft werden. Das folgende Beispiel testet Objekte auf alle in Frage kommenden Datentypen einer zwei- bzw. dreistufigen Vererbungshierarchie (bzw. einer Stufe mehr durch Object).


'...\Klassendesign\Vererbung\TypeOf.vb

Option Strict On 
Namespace Klassendesign

  Class Behausung : End Class 
  Class Höhle : Inherits Behausung : End Class 
  Class Haus : Inherits Behausung : End Class 
  Class Hochhaus : Inherits Haus : End Class

  Module Typprüfung 
    Sub Test() 
      For Each b As Behausung In New Behausung() { _ 
        New Haus(), New Höhle(), New Hochhaus(), New Behausung() _ 
      } 
        Console.Write("Typ {0}: ", b.GetType()) 
        If TypeOf b Is Object Then Console.Write("Object ") 
        If TypeOf b Is Behausung Then Console.Write("Behausung ") 
        If TypeOf b Is Haus Then Console.Write("Haus ") 
        If TypeOf b Is Hochhaus Then Console.Write("Hochhaus ") 
        If TypeOf b Is Höhle Then Console.Write("Höhle ") 
        Console.WriteLine() 
      Next 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt deutlich, dass ein Datentyp außer für sich selbst auch noch für alle Klassen innerhalb seiner Vererbungshierarchie stehen kann:

Typ Vererbung.Klassendesign.Haus: Object Behausung Haus 
Typ Vererbung.Klassendesign.Höhle: Object Behausung Höhle 
Typ Vererbung.Klassendesign.Hochhaus: Object Behausung Haus Hochhaus 
Typ Vererbung.Klassendesign.Behausung: Object Behausung

Ein Teil der Hierarchie ist Object. Dies liegt an folgender Tatsache (im Beispiel trifft dies auf Behausung zu):


Ohne explizite Angabe erbt eine Klasse von Object – oder anders betrachtet: Jede Klasse bis auf Object hat immer genau eine Elternklasse.


Implizite Typumwandlung (von Methodenparametern)

Um Arbeit zu sparen sollten Sie, soweit dies sinnvoll ist, Ihre Programmlogik mit möglichst allgemeinen Datentypen formulieren. Sollten Sie in Zukunft speziellere Kindklassen ableiten, können die einmal formulierten Programme unverändert auch mit den neuen Datentypen arbeiten, weil ja eine Kindklasse immer als Stellvertreter für eines ihrer Eltern verwendet werden kann. Kurz formuliert:


Datentypen sollten so allgemein wie möglich und so speziell wie nötig sein.


Das folgende Codefragment zeigt einen Kalorienrechner für Gebäck. Da jede Art von Gebäck Kalorien hat, ist es möglich, alle Kekse und Torten im Punkt der Kalorien gleich zu behandeln. So muss nicht für jede noch zu erfindende kulinarische Süßigkeit ein neuer Kalorienrechner geschrieben werden, sondern die neue muss nur von Gebäck abgeleitet werden. Das war’s.


'...\Klassendesign\Vererbung\Implizit.vb

Option Strict On 
Namespace Klassendesign

  Class Gebäck 
    Friend Kalorien As Short 
  End Class 
  Class Keks : Inherits Gebäck : End Class 
  Class Torte : Inherits Gebäck : End Class 
  Class Schokotorte : Inherits Torte : End Class

  Module Implizit 
    Function Kalorien(ByVal ParamArray stücke() As Gebäck) As Short 
      For Each g As Gebäck In stücke : Kalorien += g.Kalorien : Next 
    End Function 
    Sub Test() 
      Dim diätkeks As Keks = New Keks() : diätkeks.Kalorien = 30 
      Dim sacher As Schokotorte = New Schokotorte() : sacher.Kalorien = 800 
      Dim marzipan As Torte = New Torte() : marzipan.Kalorien = 700 
      Console.WriteLine("Der Nachtisch hat {0} Kalorien", _ 
                        Kalorien(sacher, marzipan, diätkeks, marzipan)) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe bestätigt: Der Diätkeks zwischendurch hilft nicht wirklich beim Abnehmen:

Der Nachtisch hat 2230 Kalorien

Innere Klassen

Ein Datentyp, der in einem anderen Datentyp definiert ist, ist komplett eigenständig. Vom Effekt her ist es, als stünde er in einem Namensraum, der den Namen des äußeren Datentyps trägt. Dies gilt auch für die Vererbung. Das folgende Beispiel zeigt eine Klasse namens Bus, in der eine Klasse Passagiere definiert ist. Mit Inherits wird von diesem Bus ein Reisebus und von der inneren Klasse Passagiere die Klasse Reisende abgeleitet.


'...\Klassendesign\Vererbung\InnereKlassen.vb

Option Strict On 
Namespace Klassendesign

  Class Bus 
    Class Passagiere 
      Friend Anzahl As Short = 120 
    End Class 
  End Class

  Class Reisebus 
    Inherits Bus 
  End Class

  Class Reisende 
    Inherits Reisebus.Passagiere 
    Friend Anzahl As Short = 80 
  End Class 
  ... 
End Namespace

Der Test prüft durch den Zugriff auf das Feld Anzahl die durch Vererbung weitergegebene Funktionalität.


'...\Klassendesign\Vererbung\InnereKlassen.vb

Option Strict On 
Namespace Klassendesign 
  ... 
  Module InnereKlassen 
    Sub Test() 
      Dim p As Reisebus.Passagiere = New Reisebus.Passagiere() 
      Console.WriteLine("Bus hat {0} Passagiere", p.Anzahl) 
      Dim r As Reisende = New Reisende() 
      Console.WriteLine("Bus hat {0} Passagiere", r.Anzahl) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe bestätigt die korrekten Vererbungsverhältnisse.

Bus hat 120 Passagiere 
Bus hat 80 Passagiere

Rheinwerk Computing - Zum Seitenanfang

3.13.2 Sichtbarkeitsmodifikatoren Zur nächsten ÜberschriftZur vorigen Überschrift

Was ich nicht weiß, macht mich nicht heiß.

Auch bei Nutzung der Vererbung kann ich nur dringend dazu raten, alles, was nicht unbedingt von außen gebraucht wird, mittels Sichtbarkeitsmodifikatoren so weit es geht zu »verstecken«. Denn wovon die Nutzer Ihrer Klassen nichts wissen, das können Sie jederzeit ändern oder korrigieren, ohne deren Programme zu beeinflussen. Davon profitieren Sie auch, denn sehr wahrscheinlich sind Sie selbst der erste Nutzer der eigenen Klassen. Die meisten Sichtbarkeitsmodifikatoren haben Sie schon in Abschnitt 3.2, »Kapselung«, kennengelernt (siehe unter anderem Tabelle 3.2, »Sichtbarkeitsmodifikatoren eines Klassenmitglieds«). In diesem Abschnitt beschäftigen wir uns mit dem noch nicht besprochenen Sichtbarkeitsmodifikator Protected und den Effekten der Vererbung auf die Sichtbarkeit. Erst im Rahmen der Polymorphie (siehe Abschnitt 3.14, »Polymorphie«) kommt es zum Zwang, eine einmal festgelegte Sichtbarkeit beizubehalten.


Ohne Polymorphie kann die Sichtbarkeit in einer Kindklasse vollständig unabhängig von der Sichtbarkeit in der Elternklasse gewählt werden. Der Einzelfall entscheidet darüber, inwieweit davon Gebrauch gemacht werden sollte.


Protected

Soll ein Klassenmitglied nur in Kindklassen sichtbar sein, wird es mit dem Modifikator Protected gekennzeichnet. Wenn es außerdem in derselben Anwendung sichtbar sein soll, spezifizieren Sie die Kombination Protected Friend. Gebraucht wird diese Sichtbarkeitsbeschränkung zum Beispiel, wenn ein Kunde in einem Webformular einen Preis für ein Produkt mit maßgeschneiderten Eigenschaften ermitteln kann und der Preis vom Produktionsprozess abhängt, der nicht offengelegt werden soll. Das nächste Codefragment zeigt die alltägliche Situation, dass der Einkaufspreis dem Endpreis nicht entnommen werden kann. Dies wird im Programm dadurch realisiert, dass man die Sichtbarkeit des Einkaufspreises mittels Protected auf die Kindklasse beschränkt.


'...\Klassendesign\Vererbung\Protected.vb

Option Strict On 
Namespace Klassendesign

  Class Schaumstoffblock 
    Protected Einkaufspreis As Double = 100 
  End Class

  Class Matratze 
    Inherits Schaumstoffblock 
    Private Marge As Double = 0.3 
    Sub Preis() 
      Console.WriteLine("Preis {0}", Einkaufspreis * (1 + Marge)) 
    End Sub 
  End Class

  Module Sichtbarkeit 
    Sub Test() 
      Dim matratze As Matratze = New Matratze() 
      matratze.Preis() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe bestätigt den korrekten Zugriff auf das mit Protected gekennzeichnete Klassenmitglied:

Preis 130

Hinweis
Datentypen ohne Vererbung (zum Beispiel Module) dürfen Protected nicht spezifizieren (Ausnahme: zur Beerbung von Object).


Zugriff

Jede Klasse kann mit einem der Sichtbarkeitsmodifikatoren Public und Friend gekennzeichnet werden (kein Modifikator ist gleichbedeutend mit Friend). Dabei ist zu beachten, dass eine Kindklasse nie sichtbarer sein darf als ihre Elternklasse. Dieses Prinzip gilt neben Klassen auch für andere Datentypen, zum Beispiel für Schnittstellen. Daher wird das folgende Codefragment vom Compiler zurückgewiesen:

Friend Class Anwendung : End Class 
Public Class Öffentlich : Inherits Anwendung : End Class 'Compilerfehler!!

Ein Kindtyp ist nie sichtbarer als die Eltern.


Lecks

Wie im realen Leben bleibt ein Geheimnis nur dann gewahrt, wenn es keine undichte Stelle gibt. Wie in Abschnitt 3.2.1, »Kombinationen«, beschrieben wurde, kommt uns Visual Basic ein Stück entgegen, indem die restriktivste Transparenz die ganze Sichtbarkeit dominiert. Es reicht also, in der gesamten Kette von Objekten und Typen, die für den Zugriff auf ein Klassenmitglied nötig sind, eines zu haben, das restriktiv genug ist. Leider ist damit noch nicht alles getan, denn es gibt zwei Konzepte in Visual Basic, die an anderer Stelle nützlich sind, hier aber die Bemühungen um möglichst kleine Sichtbarkeit torpedieren:

  • Kindklassen fügen immer nur Definitionen hinzu, aber löschen nie welche.
  • Die Sichtbarkeit wird erst bei der Verwendung berücksichtigt, nicht bereits bei der Definition.

Wenn also in einer Klasse ein Klassenmitglied öffentlich gemacht wird, ist dies nicht mehr rückgängig zu machen. Wenn eine Kindklasse diese Definition durch eine weniger sichtbare ersetzt, wirkt sich das nur auf die Kindklasse und deren Kindklassen aus. Von außen erfolgt der Zugriff auf das veröffentlichte Mitglied, und die restriktiveren Modifikationen bleiben unsichtbar. Die Tabelle 3.23 zeigt, auf welche Klasse in Abhängigkeit von den Sichtbarkeitsmodifikatoren zugegriffen wird.


Tabelle 3.23 Sichtbarkeit im Rahmen der Vererbung

Eltern Kind Enkel sieht Projekt sieht Andere sehen

Public

Private

Eltern

Eltern

Eltern

Protected

Kind

Eltern

Eltern

Friend

Eltern

Kind

Eltern

Protected Friend

Kind

Kind

Eltern

Public

Kind

Kind

Kind

Protected Friend

Private

Eltern

Eltern

Protected

Kind

Eltern

Friend

Eltern

Kind

Protected Friend

Kind

Kind

Public

Kind

Kind

Kind

Friend

Private

Eltern

Protected

Kind

Eltern

Friend

Kind

Protected Friend

Kind

Kind

Public

Kind

Kind

Kind

Protected

Private

Eltern

Protected

Kind

Friend

Eltern

Kind

Protected Friend

Kind

Kind

Public

Kind

Kind

Kind

Private

Private

Protected

Kind

Friend

Kind

Protected Friend

Kind

Kind

Public

Kind

Kind

Kind


Das folgende Codefragment zeigt eine dreistufige Vererbungshierarchie. In der untersten Stufe, Person, wird die Methode Name() öffentlich zugänglich gemacht. In der zweiten Stufe legt sich der Politiker ein Pseudonym zu, auf das in der dritten Stufe über die Methode Ansprache() zugegriffen wird. In der Methode Test() wird der Name einmal über die gleichnamige Methode sowie über die Methode Ansprache() ausgegeben.


'...\Klassendesign\Vererbung\Lecks.vb

Option Strict On 
Namespace Klassendesign

  Class Person 
    Public Sub Name() 
      Console.WriteLine("Herbert Frahm") 'bürgerlicher Name 
    End Sub 
  End Class

  Class Politiker 
    Inherits Person 
    Protected Sub Name() 
      Console.WriteLine("Willy Brandt")  'bekanntes Pseudonym 
    End Sub 
  End Class

  Class Spitzenpolitiker 
    Inherits Politiker 
    Public Sub Ansprache() 
      Name() 
    End Sub 
  End Class

  Module Lecks 
    Sub Test() 
      Dim kanzler As Spitzenpolitiker = New Spitzenpolitiker() 
      kanzler.Name() 
      kanzler.Ansprache() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die erste Ausgabe zeigt, dass der einmal mittels Public veröffentlichte bürgerliche Name auch dann angesprochen werden kann, wenn er in der Vererbungshierarchie mit einem gleichnamigen Klassenmitglied überschrieben wurde, dessen Sichtbarkeit mittels Protected auf Kindklassen beschränkt wurde. Dieses Mitglied ist von außen schlichtweg nicht sichtbar, und das nächste sichtbare Element gleichen Namens in der Vererbungshierarchie wird verwendet, hier Name() in der Klasse Person. Die zweite Ausgabe zeigt, dass Name() in der Klasse Politiker über dessen Kindklasse Spitzenpolitiker in der Methode Ansprache() erreichbar ist.

Herbert Frahm 
Willy Brandt

Daraus können wir folgern:


Sichtbarkeitsmodifikatoren bestimmen, wer zusätzlich etwas zu sehen bekommt (war vorher nichts definiert, ist das natürlich gleichzeitig eine absolute Spezifikation).



Rheinwerk Computing - Zum Seitenanfang

3.13.3 Zugriff auf Eltern mit MyBase Zur nächsten ÜberschriftZur vorigen Überschrift

Oft steckt viel Arbeit in einer Klassenmethode. Wenn dann die Klasse, die diese Methode enthält, in einer Kindklasse spezialisiert wird, kommt es häufig vor, dass die Funktionalität der Methode ein wenig erweitert werden soll, ohne ihren Namen zu ändern. Wir haben also folgende Situation:

Class Basis 
  Sub Methode() 
    ... 
  End Sub 
End Class

Class Kind 
  Inherits Basis 
  Sub Methode() 
    ... 
    Methode() 'Kind.Methode(), nicht Basis.Methode() => infinite Rekursion 
  End Sub 
End Class

Das funktioniert so aber nicht! Denn der Aufruf Methode() in Kind würde sich selbst aufrufen und nicht die gleichnamige Methode in Basis. Der Aufruf muss daher mit folgender Syntax qualifiziert werden (das Mitglied müssen Sie Ihren Bedürfnissen anpassen):


MyBase.<Klassenmitglied>


Hinweis
MyBase greift auf das in der Vererbungshierarchie nächste sichtbare Klassenmitglied zu, ein Überspringen mittels MyBase.MyBase ist nicht erlaubt.


Das folgende Codefragment zeigt, wie in der Methode Ansprache() der Klasse Professor mittels MyBase die Methode in der Elternklasse Forscher aufgerufen wird.


'...\Klassendesign\Vererbung\MyBase.vb

Option Strict On 
Namespace Klassendesign

  Class Forscher 
    Friend Name As String 
    Sub Ansprache() 
      Console.Write(Name) 
    End Sub 
  End Class

  Class Professor 
    Inherits Forscher 
    Sub Ansprache() 
      Console.Write("Professor ") 
      MyBase.Ansprache() 
    End Sub 
  End Class

  Module Basiszugriff 
    Sub Test() 
      Dim prof As Professor = New Professor() 
      prof.Name = "Habakuk Tibatong" 
      prof.Ansprache() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt, dass die Methoden beider Klassen zum Zuge kommen:

Professor Habakuk Tibatong

Konstruktorverkettung

Wenn ein Objekt einer Klasse mittels New erzeugt wird, müssen die Initialisierungen aller Ebenen der Vererbungshierarchie korrekt ablaufen. Da eine Kindklasse auf einer Elternklasse basiert, muss zuerst die Initialisierung der Elternklasse durchgeführt werden und dann erst die der Kindklasse. Ansonsten könnte sich die Kindklasse nicht sauber auf die Elternklasse stützen. Doch warum haben wir uns bisher darüber keine Gedanken gemacht? Ganz einfach: Wenn der Quelltext nichts explizit vorgibt, fügt der Compiler in jeden Konstruktor automatisch Code ein, der den parameterlosen Standardkonstruktor der Elternklasse aufruft, egal ob dieser explizit im Quelltext steht oder automatisch zur Verfügung gestellt wird, weil die Klasse keinen Konstruktor im Quelltext definiert (siehe Abschnitt 3.5, »Objektinitialisierung mit Konstruktoren«). Das folgende Codefragment zeigt die Idee:

Class Basis 
End Class 
Class Kind 
  Inherits Basis 
  Sub New() 
    MyBase.New() 'automatisch eingefügt 
    ... 
  End Sub 
End Class

Jeder Konstruktor, außer in Object, ruft in der ersten Anweisung (und nur dort) einen Konstruktor der eigenen Klasse oder der Basisklasse auf.


Dieser Mechanismus ist nicht erwünscht, wenn ein Konstruktor der Basisklasse verwendet werden soll, der Parameter erwartet. Noch kritischer ist die Situation, in der es gar keinen parameterlosen Konstruktor gibt, weil alle benutzerdefinierten Konstruktoren Parameter haben. Dann ist der automatisch eingefügte Aufruf nicht möglich. Der Compiler meldet einen Fehler, und Sie müssen einen Aufruf eines Basisklassenkonstruktors selbst einfügen. Das folgende Beispiel zeigt, wie ein durch die Basisklasse Fenster im Konstruktor erzwungener Fenstertitel von der Kindklasse Startfenster weitergegeben wird:


'...\Klassendesign\Vererbung\Konstruktorverkettung.vb

Option Strict On 
Namespace Klassendesign

  Class Fenster 
    Private titel As String 
    Sub New(ByVal titel As String) 
      Me.titel = titel 
    End Sub 
    Sub Fenstertitel() 
      Console.WriteLine(titel) 
    End Sub 
  End Class

  Class Startfenster 
    Inherits Fenster 
    Sub New(ByVal titel As String) 
      MyBase.New(titel) 
    End Sub 
  End Class

  Module Konstruktorverkettung 
    Sub Test() 
      Dim fenster As Startfenster = New Startfenster("Dokument 1") 
      fenster.Fenstertitel() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe bestätigt den korrekten Zugriff:

Dokument 1

Damit die Initialisierungsreihenfolge – erst Eltern und dann Kinder – gewahrt bleibt, muss der Aufruf die erste Anweisung im Konstruktor sein. Das Erfordernis, dass eine Konstruktorverkettung die erste Anweisung sein muss, kann nicht umgangen werden. Zum Beispiel ist es nicht möglich, das zu erstellende Objekt (implizit) in den aktuellen Parametern von New zu verwenden. Der folgende Code würde vom Compiler zurückgewiesen:

Function Seiteneffekt() As Short 
  Return 5 
End Function 
Sub New() 
  MyBase.New(Seiteneffekt()) 'Fehler!! 
End Sub

Hinweis
Klassenkonstruktoren (Shared Sub New()) können nicht verkettet werden, da die Laufzeitumgebung die Ladereihenfolge von Datentypen selbst bestimmt.


Protected-Konstruktoren

Bei der Zusammenfassung gemeinsamer Funktionalität in einer Elternklasse kann diese einen derart hohen Abstraktionsgrad erreichen, dass es nicht sinnvoll ist, von dieser Klasse Objekte zu erzeugen. Die Objekterzeugung kann dann einfach durch einen nichtöffentlichen Objektkonstruktor unterbunden werden. Soll außerdem die Entwicklung von Kindklassen ungehindert möglich sein, wählen Sie Protected als Spezifikation für die Sichtbarkeit. Das folgende Beispiel zeigt eine Basisklasse Ware mit einem Konstruktor, der die Bezeichnung der Ware festlegt. Da diese Klasse so allgemein ist, dass sie selbst Geschenke und zu verkaufende Sachen umfassen soll, ist der Konstruktor durch Protected nicht öffentlich. Die Kindklassen Präsent und Verkaufsartikel haben außer der Warenbezeichnung nichts gemeinsam. Durch die gemeinsame Basisklasse ist aber gewährleistet, dass es keine Ware ohne Bezeichnung gibt.


'...\Klassendesign\Vererbung\ProtectedKonstruktor.vb

Option Strict On 
Namespace Klassendesign

  Class Ware 
    Protected Name As String 
    Protected Sub New(ByVal name As String) 
      Me.Name = name 
    End Sub 
  End Class

  Class Präsent 
    Inherits Ware 
    Sub New(ByVal name As String) 
      MyBase.New(name) 
    End Sub 
    Sub Angebot() 
      Console.WriteLine("{0} als Präsent", Name) 
    End Sub 
  End Class

  Class Verkaufsartikel 
    Inherits Ware 
    Private Preis As Double 
    Sub New(ByVal name As String, ByVal preis As Double) 
      MyBase.New(name) : Me.Preis = preis 
    End Sub 
    Sub Kosten() 
      Console.WriteLine("{0} kostet {1}", Name, Preis) 
    End Sub 
  End Class 
  ... 
End Namespace

Zum Testen werden zwei spezifische Waren erzeugt und wird deren spezifische Methode aufgerufen:


'...\Klassendesign\Vererbung\ProtectedKonstruktor.vb

Option Strict On 
Namespace Klassendesign 
  ... 
  Module ProtectedKonstruktor 
    Sub Test() 
      Dim stift As Präsent = New Präsent("Kugelschreiber") 
      Dim drucker As Verkaufsartikel = New Verkaufsartikel("Drucker", 350) 
      stift.Angebot() 
      drucker.Kosten() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Beschreibungen der Waren sind recht unterschiedlich, greifen aber beide auf die Bezeichnung der Ware zurück:

Kugelschreiber als Präsent 
Drucker kostet 350

Ereignisse

Eine Kindklasse kann behandelnde Methoden für Ereignisse einer Elternklasse registrieren, auch wenn die Methoden mit Shared klassengebunden sind. Die Kindklasse kann das Ereignis der Elternklasse aber nicht auslösen. Das folgende Codefragment wird daher vom Compiler zurückgewiesen:

  Class Ausbilder 
    Event Zählen() 
  End Class 
  Class Lehrer 
    Inherits Ausbilder 
    Sub Appell() 
      RaiseEvent Zählen() 'Fehler!! 
    End Sub 
  End Class

Rheinwerk Computing - Zum Seitenanfang

3.13.4 Modifikation: Shadows und Overloads (Overrides) Zur nächsten ÜberschriftZur vorigen Überschrift

Wenn eine Kindklasse das von einer Basisklasse Geerbte modifizieren will, hat sie dazu drei Möglichkeiten. In diesem Abschnitt werden die beiden ersten behandelt, die dritte wird in Abschnitt 3.14, »Polymorphie«, folgen. Die Modifikatoren schließen sich gegenseitig aus.

  • Neudefinition mit Shadows: Alle Definitionen der Basisklasse für ein Symbol werden aufgegeben (Verdeckung des Namens).
  • Änderung mit Overloads: Zusätzliche Definitionen erweitern die Definitionen der Basisklasse für ein Symbol, oder einzelne Definitionen der Basisklasse für ein Symbol werden ersetzt (Verdeckung der Signatur).
  • Ersatz mit Overrides: Eine einzelne Definition für ein Symbol wird effektiv »in« der Basisklasse selbst ersetzt.

Hinweis
Shadows und Overloads dürfen selbst dann verwendet werden, wenn die Basisklasse keine korrespondierenden Mitglieder hat. Ohne Angaben zur Modifikation wird Shadows angenommen.



Hinweis
Alle Signaturen eines Symbols müssen entweder mit Shadows (explizit oder implizit) oder mit Overloads bzw. Overrides gekennzeichnet werden. Eine Mischung der beiden »Gruppen« ist nicht erlaubt.


Bisher haben wir keine der drei Modifikatoren angegeben, und der Compiler hat implizit eine Spezifikation von Shadows angenommen. Das ist bisher nicht aufgefallen, weil in der Basisklasse für ein Symbol nur eine einzige Definition vorlag.

Das nächste Beispiel zeigt die Unterschiede der Modifikatoren. Dazu wird bei einem allgemeinen Flugzeug bei der Begrüßung der Passagiere zwischen einer mittels des Datentyps Byte auf 255 Plätze beschränkten Maschine und einer allgemeinen Maschine unterschieden, die den Datentyp Short verwendet. Ein Linienflugzeug leitet sich vom allgemeinen Flugzeug mittels Inherits ab und stellt nur eine Art der Begrüßung zur Verfügung, indem es mittels Shadows die Definitionen in Flugzeug verwirft. Demgegenüber wird beim Charterflugzeug mittels Overloads nur die Definition für eine Methodensignatur ersetzt.


'...\Klassendesign\Vererbung\Modifikation.vb

Option Strict On 
Namespace Klassendesign

  Class Flugzeug 
    Sub Einchecken(ByVal anzahl As Byte) 
      Console.Write("{0} per Handschlag ", anzahl) 
    End Sub 
    Sub Einchecken(ByVal anzahl As Short) 
      Console.Write("{0} per Gruß ", anzahl) 
    End Sub 
  End Class

  Class Linienflugzeug 
    Inherits Flugzeug 
    Shadows Sub Einchecken(ByVal anzahl As Short) 
      Console.Write("{0} Begrüßungen ", anzahl) 
    End Sub 
  End Class

  Class Charterflugzeug 
    Inherits Flugzeug 
    Overloads Sub Einchecken(ByVal anzahl As Short) 
      Console.Write("{0} per Ansprache ", anzahl) 
    End Sub 
  End Class 
  ... 
End Namespace

Der Test der Flugzeuge versucht, die verschiedenen Methodensignaturen anzusprechen.


'...\Klassendesign\Vererbung\Modifikation.vb

Option Strict On 
Namespace Klassendesign 
  ... 
  Module Modifikation 
    Sub Test() 
      Dim linie As Linienflugzeug = New Linienflugzeug() 
      Dim charter As Charterflugzeug = New Charterflugzeug() 
      linie.Einchecken(4) : linie.Einchecken(300) : Console.WriteLine() 
      charter.Einchecken(4) : charter.Einchecken(300) : Console.WriteLine() 
      Dim allg As Flugzeug = linie 
      allg.Einchecken(4) : allg.Einchecken(300) : Console.WriteLine() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die erste Zeile der Ausgabe zeigt, dass die Neudefinition mit Shadows die Signatur mit Byte komplett verdrängt hat. Dagegen ist in der zweiten Zeile zu sehen, dass mit Overloads nur die eine Signatur ersetzt wurde. Schließlich führt die Verwendung einer Referenz vom Typ der Basisklasse dazu, dass auch die Definitionen verwendet werden, die in der Basisklasse bekannt sind. Wie Letzteres vermieden werden kann, wird in Abschnitt 3.14, »Polymorphie«, erklärt.

4 Begrüßungen 300 Begrüßungen 
4 per Handschlag 300 per Ansprache 
4 per Handschlag 300 per Gruß

Klassenbindung mit Shared

Die Modifikation eines Klassenmitglieds ist nicht auf objektgebundene Mitglieder beschränkt. Das folgende Beispiel zeigt den Ausdruck eines Feldes von Zahlen durch Methoden, die mittels Shared an die Klasse gebunden sind. Werden diese in der Klasse ReelleZahl als reelle Zahl interpretiert, wird nur der erste Wert ausgegeben. Ist es hingegen eine komplexe Zahl wie in der Klasse KomplexeZahl, werden die ersten beiden Werte als Summe aus beiden ausgedruckt. Sollten Sie nicht mit komplexen Zahlen vertraut sein: Hier kommt es nur auf das Konzept an, wie die Methoden vererbt werden, und nicht darauf, was der Begriff »komplexe Zahl« bedeutet.


'...\Klassendesign\Vererbung\Shared.vb

Option Strict On 
Namespace Klassendesign

  Class ReelleZahl 
    Shared Sub Druck(ByVal v() As Double) 
      Console.Write("Wert {0}    ", v(0)) 
    End Sub 
  End Class

  Class KomplexeZahl 
    Inherits ReelleZahl 
    Overloads Shared Sub Druck(ByVal v() As Double, ByVal reell As Boolean) 
      If reell Then Druck(v) Else _ 
        Console.Write("Wert {0}+I*{1}   ", v(0), v(1)) 
    End Sub 
  End Class

  Module Klassengebunden 
    Sub Test() 
      Dim zahl() As Double = New Double() {8.1, 5.2} 
      KomplexeZahl.Druck(zahl, True) : KomplexeZahl.Druck(zahl, False) 
      KomplexeZahl.Druck(zahl) : ReelleZahl.Druck(zahl) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Bis auf die erste Ausgabe werden die Werte durch die Funktion der Basisklasse erzeugt. Beim Aufruf über die Kindklasse erfolgt eine Weiterleitung von Druck(v) an die Elternklasse.

Wert 8.1+I*5.2   Wert 8.1    Wert 8.1    Wert 8.1

Komplettersatz mit Shadows

Die Kennzeichnung mit Shadows verdeckt ein Symbol in einer Basisklasse vollständig, inklusive aller mit ihm verbundenen Signaturen. Das geht so weit, dass selbst die Art der Definition geändert werden kann. So kann zum Beispiel aus einer Prozedur ein Feld werden, wie das nächste Codefragment zeigt. Wenn die Referenzvariable vom Typ Ministerpräsident ist, wird in dem Beispiel das Feld angesprochen. Ist sie vom Typ Freund, kommt die Prozedur zum Zuge. Damit wird bereits durch die Klassendefinition sichergestellt, dass der Amtsträger Rau »falsch« angesprochen wird. Jeder Quelltext, der diese Klassen verwendet, kann also diesbezüglich keine »Fehler« machen. Dieses Konzept, Fehler möglichst unmöglich zu machen, schafft insgesamt viel robustere Programme.


'...\Klassendesign\Vererbung\Shadows.vb

Option Strict On 
Namespace Klassendesign

  Class Freund 
    Friend Sub Name(ByVal wer As String) 
      Select Case wer 
        Case "Freund" : Console.WriteLine("Hallo Bruder Johannes") 
        Case Else : Console.WriteLine("Herr Johannes Rau") 
      End Select 
    End Function 
  End Class

  Class Ministerpräsident 
    Inherits Freund 
    Friend Shadows Name As String = "Johannes Rau" 
  End Class

  Module Neudefinition 
    Sub Test() 
      Dim präsident As Ministerpräsident = New Ministerpräsident() 
      Dim freund As Freund = präsident 
      Console.WriteLine("Herr {0}", präsident.Name) 
                                    freund.Name("Freund") 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die erste Ausgabe gibt das Feld Name der Klasse Ministerpräsident aus, die zweite wird durch die Prozedur Name() der Klasse Freund erzeugt:

Herr Johannes Rau 
Hallo Bruder Johannes

Hinweis
Eine Kindklasse hat keine Möglichkeit, einmal gemachte Definitionen in der Elternklasse zu löschen, aber mit Shadows können sie komplett ersetzt werden. Alle von der Kindklasse abgeleiteten Klassen haben dann keinen Zugriff mehr auf die mit Shadows verdeckten Symbole der Elternklasse, wenn die neue Definition für sie sichtbar ist.


Private und Shadows

Obwohl immer alles komplett vererbt wird, gilt dies nicht für die Sichtbarkeit des Geerbten:


Private-Klassenmitglieder stehen in einer Kindklasse nicht zur Verfügung.


Das macht sich in einer mehrstufigen Vererbungshierarchie bemerkbar. Wenn ein Symbol mittels Shadows komplett überdeckt wird, hat dies nur dann einen Einfluss auf Kindklassen, wenn das Klassenmitglied nicht privat ist, da alle privaten Mitglieder nur in der Klasse selbst sichtbar sind. Dies führt im folgenden Beispiel dazu, dass der Aufruf Aktion() in der Klasse Aktionen die Methode in der Klasse Button anspricht und nicht die private Methode in Menüeintrag.


'...\Klassendesign\Vererbung\PrivateShadows.vb

Option Strict On 
Namespace Klassendesign

  Class Button 
    Sub Aktion() 
      Console.WriteLine("Aktion") 
    End Sub 
  End Class

  Class Menüeintrag : Inherits Button 
    Private Shadows Sub Aktion() 
      Console.WriteLine("Menü hervorheben") 
    End Sub 
  End Class

  Class Aktionen : Inherits Menüeintrag 
    Sub Speichern() 
      Aktion() 
    End Sub 
  End Class

  Module PrivatVerdeckt 
    Sub Test() 
      Dim akt As Aktionen = New Aktionen() 
      akt.Speichern() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe bestätigt das Überspringen der privaten Methode.

Aktion

Overloads und ParamArray

Eine Methode mit einem ParamArray-Parameter akzeptiert eine variable Anzahl an aktuellen Parametern. Wenn eine solche Methode mittels Overloads eine Methodensignatur in einer Elternklasse mit einer fixen Anzahl Parameter gleichen Typs ergänzt, überdeckt sie diese nicht, sondern stellt eine zusätzliche Signatur dar, die nur dann aufgerufen wird, wenn die Methode der Elternklasse nicht passt. Kurz formuliert:


ParamArray überschreibt ParamArray, nicht alle repräsentierten Signaturen.


Das folgende Codefragment überlädt in der Kindklasse MotorradMitBeiwagen die Methode Mitfahrer() der Elternklasse Motorrad. Beide Methoden akzeptieren im Prinzip den Aufruf mit einem einzelnen String-Parameter. Dieser Aufruf wird in der Methode Test() ausprobiert.


'...\Klassendesign\Vererbung\ParamArray.vb

Option Strict On 
Namespace Klassendesign

  Class Motorrad 
    Overloads Sub Mitfahrer(ByVal sozius As String) 
      Console.WriteLine("{0} fährt hinten auf dem Motorrad", sozius) 
    End Sub 
  End Class

  Class MotorradMitBeiwagen 
    Inherits Motorrad 
    Overloads Sub Mitfahrer(ByVal ParamArray sozius() As String) 
      Console.WriteLine("{0} fährt mit", sozius(0)) 
    End Sub 
  End Class

  Module Opitionales 
    Sub Test() 
      Dim moto As MotorradMitBeiwagen = New MotorradMitBeiwagen() 
      moto.Mitfahrer("Miss Piggy") 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt, dass die Methode in der Elternklasse angesprochen wird, da sie spezifischer ist und nicht von der ParamArray-Signatur verdeckt wird.

Miss Piggy fährt hinten auf dem Motorrad

Hinweis
Parametersignaturen mit Optional sind nicht betroffen.



Rheinwerk Computing - Zum Seitenanfang

3.13.5 Abstrakte Klassen: MustInherit und MustOverride Zur nächsten ÜberschriftZur vorigen Überschrift

Bis jetzt waren die Entscheidungen freiwillig, was wie ver- oder geerbt wurde. Es gibt aber häufig Situationen, in denen man einen gewissen Zwang ausüben möchte. Zum Beispiel kann eine Klasse eine Elternklasse für eine ganze Gruppe von Kindklassen sein. Prinzipbedingt erfasst dann die in der Elternklasse formulierte gemeinsame Funktionalität nicht alle Spezialitäten der Kindklasse, auch wenn sie gleich benannt ist. In solchen Situationen ist es nicht möglich, die Elternklasse komplett auszuformulieren. Da eine solche »unvollständige« Klasse auch »unvollständige« Objekte erzeugen würde, unterbindet man mittels des Modifikators MustInherit die Möglichkeit der Objekterzeugung. Dennoch kann, in Bezug auf die Namensgebung, die Elternklasse vervollständigt werden, sodass zumindest die Schnittstelle, die zur Verwendung von Klassenmitgliedern nötig ist, festgelegt ist. Jedes Klassenmitglied in der Elternklasse, das zwar formell, aber nicht inhaltlich festgelegt werden kann, wird nur als Signatur mit dem Modifikator MustOverride in die Klasse geschrieben. Es gibt folgende vier Syntaxvarianten (optionale Teile sind in eckige Klammern gesetzt, kursive Teile müssen Sie Ihren Bedürfnissen anpassen, ein Unterstrich verbindet wie üblich Teile derselben logischen Zeile):


MustInherit Class Klasse 
  MustOverride [<Modifikatoren>] Sub Methode([<Parameter>]) [<Handles>] 
  MustOverride [<Modifikatoren>] Function _ 
    Funktion([<Parameter>]) As Rückgabetyp [<Handles>] 
  MustOverride [ReadOnly|WriteOnly] [<Modifikatoren>] Property _ 
    Eigenschaft([<Parameter>]) As Rückgabetyp 
  MustOverride [<Modifikatoren>] [ReadOnly|WriteOnly] Default Property _ 
    Eigenschaft(<Parameter>) As Rückgabetyp 
End Class

Die erlaubten Modifikatoren sind recht beschränkt (siehe Tabelle 3.24).


Tabelle 3.24 Modifikatoren abstrakter Klassenmitglieder

Art Beschreibung

Sichtbarkeit

Grad der Öffentlichkeit (sieheAbschnitt 3.2, »Kapselung«), nicht: Private.

Redefinition

Art des Ersatzes (siehe Abschnitt 3.13.4, »Modifikation: Shadows und Overloads (Overrides)«, und Abschnitt 3.14.1, »Virtuelle Methoden: Overridable und Overrides«), nicht: Overridable.


Dabei ist Folgendes zu beachten:

  • Eine MustInherit-Klasse kann keine Objekte erzeugen, aber ist ein gültiger Datentyp.
  • Eine Klasse mit MustOverride-Mitgliedern muss als MustInherit gekennzeichnet werden, aber eine MustInherit-Klasse kann ohne MustOverride-Mitglieder sein.
  • Felder, Ereignisse, Delegates, Typen, Operatoren und Konstruktoren sind nie abstrakt.
  • Abstrakte Klassenmitglieder sind nie mit Shared klassengebunden.
  • Es gibt keine abstrakten externen Funktionen.
  • Eine Kindklasse darf MustOverride-Mitglieder nur mit Overrides implementieren, eine Verdeckung des Symbols mit Shadows (implizit oder explizit) ist nicht erlaubt.
  • Eine Kindklasse muss entweder alle MustOverride-Mitglieder der Elternklasse implementieren oder selbst mit MustInherit gekennzeichnet werden.
  • Die Sichtbarkeit von MustOverride-Mitgliedern muss in der Eltern- und der Kindklasse identisch sein (Ausnahme: Protected Friend wird in einer anderen Anwendung zu Protected).
  • Private MustOverride ist verboten, ebenso wie MustInherit NotInheritable.
  • MustOverride Overrides ist erlaubt, wenn die Elternklasse ein passendes Mitglied hat.

Hinweis
Eine abstrakte Klasse darf vollständige Mitglieder ohne MustOverride enthalten.


Das folgende Codefragment zeigt als Beispiel die abstrakte Klasse Luftfahrzeug, die eine Methode Abheben() definiert, denn jedes Fluggerät erhebt sich irgendwie in die Lüfte, unabhängig davon, wie das im Einzelnen passiert. Da man beim Flugdrachen zum Abheben erst Anlauf nehmen muss und ein Hubschrauber sich ohne Muskelkraft senkrecht in die Luft erheben kann, ist es nicht sinnvoll, für beide in der allgemeinen Methode Abheben() ein gemeinsames Startverhalten zu definieren. Da aber dennoch auf anderen Gebieten Gemeinsamkeiten existieren, bietet es sich an, diese in die allgemeine Klasse Luftfahrzeug zu packen. Zum Beispiel haben beide Fluggeräte einen Piloten. Daher greifen die von Luftfahrzeug abgeleiteten Klassen Flugdrachen und Hubschrauber auf diesen »allgemeinen« Piloten zurück.


'...\Klassendesign\Vererbung\Abstrakt.vb

Option Strict On 
Namespace Klassendesign

  MustInherit Class Luftfahrzeug 
    Protected pilot As String 
    Sub New(ByVal pilot As String) 
      Me.pilot = pilot 
    End Sub 
    MustOverride Sub Starten() 
  End Class

  Class Flugdrachen 
    Inherits Luftfahrzeug 
    Sub New(ByVal pilot As String) 
      MyBase.New(pilot) 
    End Sub 
    Public Overrides Sub Starten() 
      Console.WriteLine("{0} nimmt Anlauf und startet.", pilot) 
    End Sub 
  End Class

  Class Hubschrauber 
    Inherits Luftfahrzeug 
    Sub New(ByVal pilot As String) 
      MyBase.New(pilot) 
    End Sub 
    Public Overrides Sub Starten() 
      Console.WriteLine("{0} fragt den Tower und startet senkrecht.", pilot) 
    End Sub 
  End Class 
  ... 
End Namespace

Zum Test werden zwei Fluggeräte erzeugt und gestartet. Bitte beachten Sie, dass beide Variablen als Datentyp das allgemeine Luftfahrzeug haben. Da in diesem bereits die Form des Aufrufs von Starten() steht, kann die Methode aufgerufen werden, auch wenn noch gar kein Methodenrumpf existiert. Welche Methode konkret aufgerufen wird, bestimmt Visual Basic automatisch, da selbst bei einer Referenz auf den Basistyp Luftfahrzeug der reale Typ des Objekts nicht verloren geht und somit alle Informationen vorliegen, um die richtige Methode wählen zu können.


'...\Klassendesign\Vererbung\Abstrakt.vb

Option Strict On 
Namespace Klassendesign 
  ... 
  Module Abstrakt 
    Sub Test() 
      Dim drachen As Luftfahrzeug = New Flugdrachen("Otto Lilienthal") 
      Dim hubschrauber As Luftfahrzeug = New Hubschrauber("Igor Sikorsky") 
      drachen.Starten() 
      hubschrauber.Starten() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt, dass die zum Objekt passende Methode gewählt wurde:

Otto Lilienthal nimmt Anlauf und startet. 
Igor Sikorsky fragt den Tower und startet senkrecht.

Hinweis
Um den Code besser lesbar zu halten, sollte MustInherit in jedem Teil einer mit Partial aufgeteilten Klasse angegeben werden.


Handles-Klausel

Eine abstrakte Methode darf sich mittels Handles als eine Methode registrieren, die ein Ereignis behandelt. Dann sollte die implementierende Methode in einer Kindklasse sich nicht registrieren, da das Ereignis sonst doppelt behandelt wird. Das folgende Codefragment zeigt, wie die mit MustOverride als abstrakt markierte Methode Personenschutz() sich als behandelnde Methode für das Ereignis Bremsen registriert, während die Implementation in Auto dies unterlässt.


'...\Klassendesign\Vererbung\AbstraktUndHandles.vb

Option Strict On 
Namespace Klassendesign

  MustInherit Class Fahrzeug 
    Event Bremsen() 
    MustOverride Sub Personenschutz() Handles Me.Bremsen 
    Sub Gefahr() 
      RaiseEvent Bremsen() 
    End Sub 
  End Class

  Class Auto 
    Inherits Fahrzeug 
    Public Overrides Sub Personenschutz() 'kein Handles 
      Console.WriteLine("Gurt straffen!") 
    End Sub 
    Shared Sub Test() 
      Dim auto As Auto = New Auto() 
      auto.Gefahr() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt, dass das Ereignis behandelt wurde:

Gurt straffen!

Konstruktoren

Hat eine Klasse keine MustOverride-Mitglieder, ist aber mit MustInherit gekennzeichnet, können als Alternative auch alle Konstruktoren mit Protected der Öffentlichkeit entzogen werden, um eine Objekterzeugung zu unterbinden. Welche der beiden Varianten Sie wählen, ist eher Geschmackssache.


Rheinwerk Computing - Zum Seitenanfang

3.13.6 Spezifische Catch-Blöcke Zur nächsten ÜberschriftZur vorigen Überschrift

Ein Catch-Block einer Fehlerbehandlung erwartet einen Parameter vom Typ Exception oder dessen Kindklassen. Da außerdem in einem Try/Catch-Block nur der erste passende Catch-Block zum Zuge kommt, bietet sich über die Vererbungshierarchie eine gestaffelte Fehlerbehandlung an. Die ersten Catch-Blöcke kümmern sich um Fehler, die durch ihren Typ genau spezifiziert sind. In den folgenden Blöcken werden dann die Fehler immer unspezifischer. Es bietet sich oft an, den allgemeinsten Fehler, Exception, im letzten Block abzufangen, um damit alle bisher nicht behandelten Ausnahmen zu verarbeiten.

Als Beispiel werden I/O-Ausnahmen gewählt. Die folgende Vererbungshierarchie ist ein Auszug (die Namensräume sind fett gedruckt):

System-Exception 
        +-System-SystemException 
                  +System.IO-IOException 
                              +System.IO-+DirectoryNotFoundException 
                                         +DriveNotFoundException 
                                         +EndOfStreamException 
                                         +FileLoadException 
                                         +FileNotFoundException 
                                         +PathTooLongException

Das folgende Codefragment fängt in der Methode Fehlerreport() pro Stufe der Hierarchie jeweils eine Ausnahme ab. Von oben nach unten werden die Ausnahmen allgemeiner, wir bewegen uns also in der Vererbungshierarchie nach oben. Der komplexe Aufruf mit Invoke ist zu Demonstrationszwecken im Beispiel, weil er eine Ausnahme erzeugen kann, die nicht vom Typ SystemException ist.


'...\Klassendesign\Vererbung\Catch.vb

Option Strict On 
Namespace Klassendesign 
  Module Auffangen 
    Sub Aus(ByVal name As String, ByVal ex As Exception, ByVal wo As String) 
      Console.WriteLine("{0,14} => {1} ({2})", name, ex.GetType().Name, wo) 
    End Sub

    Sub Fehlerreport(ByVal name As String) 
      Try 
        Dim f As System.IO.FileInfo = New System.IO.FileInfo(name) 
        Dim len As Long = f.Length 
        GetType(Auffangen).GetMethod("Aus").Invoke(Nothing, New Object() {}) 
      Catch ex As System.IO.FileNotFoundException 
        Aus(name, ex, "FileNotFoundException") 
      Catch ex As System.IO.IOException 
        Aus(name.Substring(0, 10) & "...", ex, "IOException") 
      Catch ex As SystemException 
        Aus(name, ex, "SystemException") 
      Catch ex As Exception 
        Aus(name.Substring(0, 10) & "...", ex, "Exception") 
      End Try 
    End Sub

    Sub Test() 
      Fehlerreport("C:\\GibtsNicht") 
      Fehlerreport("C:\\" & New String("x"c, 300)) 
      Fehlerreport("C:\\?.txt") 
      Fehlerreport(My.Application.Info.LoadedAssemblies(0).Location) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt jeweils in den Klammern, in welchem Catch-Zweig eine Ausnahme abgefangen wurde.

C:\\GibtsNicht => FileNotFoundException (FileNotFoundException) 
 C:\\xxxxxx... => PathTooLongException (IOException) 
     C:\\?.txt => ArgumentException (SystemException) 
 C:\WINDOWS... => TargetParameterCountException (Exception)

Rheinwerk Computing - Zum Seitenanfang

3.13.7 Eigene Ausnahmen Zur nächsten ÜberschriftZur vorigen Überschrift

Es lohnt sich, von Anfang der Programmentwicklung an die Fehlerbehandlung mit zu berücksichtigen. Nach meiner Erfahrung ist es dazu hilfreich, eine eigene Vererbungshierarchie für Ausnahmen aufzubauen. Damit ist es möglich, im Fehlerfall bereits am Datentyp der Ausnahme zu erkennen, welchem Quelltextzusammenhang der Fehler zuzuordnen ist. Außerdem können dann in Try/Catch-Strukturen gezielt die Fehler gefiltert werden, ohne andere Fehlerquellen gleich mit berücksichtigen zu müssen. Alle eigenen Ausnahmen müssen von Exception oder dessen Kindklassen abgeleitet sein, wie die folgende Syntax zeigt:


Class Ausnahme 
  Inherits Exception 
  ... 
End Class 
Class SpezifischeAusnahme 
  Inherits Ausnahme 
  ... 
End Class


Hinweis
Alle Ausnahmen haben Exception als Vorfahren.


Als Beispiel wählen wir einen Mitarbeiter, der auch mal in Urlaub oder krank ist (Feld zustand). Das folgende Codefragment enthält auch die möglichen Ausnahmeklassen. NichtDa ist auf der obersten Ebene und von Exception abgeleitet. Die Klasse ist abstrakt, um zu verhindern, dass eine grundlose Abwesenheit vorkommen kann. Der Aufruf MyBase.New(grund) sorgt dafür, dass das Feld Message von Exception initialisiert wird. Die beiden Klassen Urlaub und Krank spezialisieren die allgemeinere Ausnahmeklasse NichtDa.


'...\Klassendesign\Vererbung\Exception.vb

Option Strict On 
Namespace Klassendesign

  Class Mitarbeiter 
    Friend zustand As String = "OK" 
    Friend name As String = "Werner" 
  End Class

  MustInherit Class NichtDa : Inherits Exception 
    Sub New(ByVal grund As String) 
      MyBase.New(grund) 'sonst ist Feld Message nicht sinnvoll belegt 
    End Sub 
  End Class

  Class Urlaub : Inherits NichtDa 
    Sub New(ByVal arb As Mitarbeiter) 
      MyBase.New(arb.name & " hat Urlaub!") 
    End Sub 
  End Class

  Class Krank : Inherits NichtDa 
    Sub New(ByVal arb As Mitarbeiter) 
      MyBase.New(arb.name & " ist krank!") 
    End Sub 
  End Class 
  ... 
End Namespace

Bei der Ausführung der Aufgaben wird der Zustand des Mitarbeiters geprüft, und gegebenenfalls wird mit Throw eine Ausnahme ausgelöst.


'...\Klassendesign\Vererbung\Exception.vb

Option Strict On 
Namespace Klassendesign 
  ... 
  Module Aufgaben 
    Sub NeueHeizung(ByVal arb As Mitarbeiter) 
      Select Case arb.zustand 
        Case "Urlaub" : Throw New Urlaub(arb) 
        Case Else : Console.WriteLine("{0} erledigt den Job.", arb.name) 
      End Select 
    End Sub

    Sub Rohrbruch(ByVal arb As Mitarbeiter) 
      Select Case arb.zustand 
        Case "krank" : Throw New Krank(arb) 
        Case Else : Console.WriteLine("{0} erledigt den Job.", arb.name) 
      End Select 
    End Sub 
  End Module 
  ... 
End Namespace

Die Aufgabenverteilung kann, je nach Zustand des Mitarbeiters, zu einer Ausnahme führen. Diese wird mit Catch aufgefangen. Da die Ausnahme Urlaub nur während der Methode NeueHeizung ausgelöst werden kann, ist beim ersten Catch auch direkt klar, dass die Ausnahme nur von dort stammen kann, denn eine Ausnahme Krank würde von diesem Catch schlicht ignoriert. Durch den allgemeineren Ausnahmetyp im zweiten Catch kann nicht direkt auf die Methode geschlossen werden, die die Ausnahme verursacht hat. Durch eine genauere Analyse des Parameters ex oder des Meldungstextes geht das.


'...\Klassendesign\Vererbung\Exception.vb

Option Strict On 
Namespace Klassendesign 
  ... 
  Module Ausnahme 
    Sub Test() 
      Dim werner As Mitarbeiter = New Mitarbeiter() 
      NeueHeizung(werner) 
      werner.zustand = "Urlaub" 
      Try 
        Rohrbruch(werner) 
        NeueHeizung(werner) 
      Catch ex As Urlaub 
        Console.WriteLine("Röhrich macht das selbst, denn {0}", ex.Message) 
      End Try 
      werner.zustand = "krank" 
      Try 
        Rohrbruch(werner) 
        NeueHeizung(werner) 
      Catch ex As NichtDa 
        Console.WriteLine("Röhrich macht das selbst, denn {0}", ex.Message) 
      End Try 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Hinweis
Der Compiler gibt keine Hinweise, ob eventuell Ausnahmen nicht behandelt werden.



Rheinwerk Computing - Zum Seitenanfang

3.13.8 Arrays Zur nächsten ÜberschriftZur vorigen Überschrift

Wenn sich die Elemente zweier Arrays von Referenztypen implizit ineinander umwandeln lassen, gilt dies auch für das ganze Array.


a As A = New B() erlaubt  =>  a() As A = New B(){…} erlaubt


Hinweis
Diese sogenannte Kovarianz von Arrays gilt nicht für Werttypen.


Bei Methodenparametern, die mit ByRef deklariert sind, gibt es eine kleine Stolperfalle in Kombination mit Arrays. Diese müssen homogen sein, das heißt, alle Datentypen der Elemente müssen identisch sein. Eine Methode mit einem ByRef-Parameter gibt ein Objekt vom deklarierten Typ zurück. Wenn der aktuelle Parameter ein Feldelement mit einem Datentyp ist, der sich nicht implizit aus dem ByRef-Parameter umwandeln lässt, wird eine Ausnahme ausgelöst, da die eben erwähnte Rückgabe das Feld inhomogen machen würde. Das folgende Codefragment nutzt eine Methode Zuwendung() mit einem ByRef-Parameter, die in Test() einmal mit einer »normalen« Referenz und dann mit einem Feldelement aufgerufen wird.


'...\Klassendesign\Vererbung\ByRef.vb

Option Strict On 
Namespace Klassendesign 
  Module ByReference 
    Sub Zuwendung(ByRef tier As Object) 
      Console.WriteLine("Streichle {0}.", tier) 
      tier = tier.ToString() & " ist gestreichelt" 
    End Sub

    Sub Test() 
      Dim Tier As Object = "Katze" 
      Dim Zoo() As Object = New String(0) {"Löwe"} 
      Zuwendung(Tier) 
      Try 
        Zuwendung(Zoo(0)) 
      Catch ex As Exception 'System.ArrayTypeMismatchException 
        Console.WriteLine("Ausnahme {0}: {1}", ex.GetType(), ex.Message) 
      End Try 
      Console.WriteLine(Tier) : Console.WriteLine(Zoo(0)) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die erste Ausgabe zeigt, wie in der Methode Zuwendung() der Parameter vom Object das Tier vom Typ String akzeptiert. Demgegenüber erzeugt der Aufruf mit einem Feldelement gleichen Typs eine Ausnahme. Das Fehlen der Ausgabe zeigt, dass die Prüfung auf Datenkonsistenz eines Feldes schon während der Parameterübergabe erfolgt. Analog zeigen die letzten beiden Ausgaben, dass der erste Aufruf das Objekt geändert hat und der zweite nicht:

Streichle Katze.

Ausnahme System.ArrayTypeMismatchException: Attempted to access an element as a  
type incompatible with the array.

Katze ist gestreichelt

Löwe

Hinweis
Ein Objekt einer Kindklasse kann ein Stellvertreter für einen Wert vom Typ der Elternklasse sein. Für einen Speicherplatz (zum Beispiel ByRef) gilt dies nicht immer.



Rheinwerk Computing - Zum Seitenanfang

3.13.9 Grafikbibliothek: neue Vielecke topZur vorigen Überschrift

In diesem Abschnitt erweitern wir die in Abschnitt 3.1.12, »Grafikbibliothek: Beispiel für Kapitel 3 und 4«, eingeführte und zuletzt in Abschnitt 3.11.6, »Grafikbibliothek: Addition und Umwandlung von Rechtecken«, erweiterte Grafikanwendung. Hier werden nur einige der Möglichkeiten der Vererbung genutzt. Zuerst wird die Idee eines Rechtecks in der Klasse Vieleck verallgemeinert. Diese abstrakte Klasse fordert das Vorhandensein einer schreibgeschützten Flächeneigenschaft. Dann wird das Rechteck von diesem Vieleck abgeleitet und diese Eigenschaft implementiert.


'...\Klassendesign\Graphik\Vererbung.vb

Option Explicit On 
Namespace Klassendesign

  Public MustInherit Class Vieleck 
    MustOverride ReadOnly Property Fläche() As Double 
  End Class

  Partial Public Class Rechteck : Inherits Vieleck 
    Overrides ReadOnly Property Fläche() As Double 
      Get 
        Return a * b 
      End Get 
    End Property 
  End Class 
  ... 
End Namespace

Damit sichergestellt ist, dass alle Seiten des Quadrats gleiche Längen haben, müssen einige Klassenmitglieder der Klasse Rechteck überschrieben werden. Sowohl der Konstruktor als auch die Größenänderungen müssen sich der neuen Situation anpassen. Um es einfach zu halten, wird ein Breitenparameter ignoriert und die Definition der Breite ohne Funktionalität überdeckt. Der Sichtbarkeitsmodifikator ist so gewählt, dass nicht die Funktionalität des Rechtecks »durchscheint« (er könnte auch Friend sein).


'...\Klassendesign\Graphik\Vererbung.vb

Option Explicit On 
Namespace Klassendesign 
  ... 
  Partial Public Class Quadrat : Inherits Rechteck

    Sub New(Optional ByVal a As Double = 1) 
      MyBase.New(a, a) 
      Größe(a, a) 
    End Sub

    Overloads Sub Größe(ByVal a As Double, Optional ByVal b As Double = 1) 
      MyBase.Größe(a, a) 
    End Sub

    Shadows Property Länge() As Double 
      Get 
        Return MyBase.Länge 
      End Get 
      Set(ByVal a As Double) 
        Größe(a, a) 
      End Set 
    End Property

    Public Shadows Breite As Double

  End Class 
End Namespace

Zum Test wird ein Quadrat erzeugt, dessen Abmessungen geändert werden, was zusätzlich protokolliert wird.


'...\Klassendesign\Zeichner\Vererbung.vb

Option Explicit On 
Namespace Klassendesign 
  Partial Class Zeichner 
    Sub Vererbung() 
      Dim q As New Quadrat(7) : q.Größe()  'Quadrat(7,7) nicht möglich 
      q.Größe(8, 9) : q.Größe()            'ignoriert zweiten Parameter 
      q.Länge = 4 : q.Größe() 
      q.Breite = 3 : q.Größe()             'durch Überdeckung ohne Einfluss 
      Console.Write("Fläche {0}", q.Fläche) 
    End Sub 
  End Class 
End Namespace

Zur Kontrolle sehen Sie hier die Ausgabe der Methode Vererbung():

Dimension des Rechtecks: 7x7 
Dimension des Rechtecks: 8x8 
Dimension des Rechtecks: 4x4 
Dimension des Rechtecks: 4x4 
Fläche 16

Die nächste Erweiterung erfolgt in Abschnitt 3.14.7, »Grafikbibliothek: Objektsammlungen«.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Visual Basic 2008
Visual Basic 2008
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Visual Basic 2012






 Visual Basic 2012


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


Zum Rheinwerk-Shop: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Rheinwerk-Shop: Windows Presentation Foundation






 Windows Presentation
 Foundation


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2009
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