In diesem Kapitel werden die Konzepte der Objektorientierung und deren Realisierung in Visual Basic erläutert. Dies umfasst alle Klassenelemente und die Vererbung.
3 Klassendesign
3.1 Objektorientierung 

3.1.1 Einführung 

Im letzten Kapitel haben Sie die Sprachelemente von Visual Basic kennengelernt, die auch ohne Objektorientierung verständlich sind. In diesem Abschnitt werden Sie endlich die Welt der Objekte betreten. Was hat es damit auf sich?
Am besten lassen sich Objekte in der Softwaretechnik anhand von vier ihrer grundlegenden Konzepten beschreiben:
- Abstrakter Datentyp Nach außen ist ein Objekt durch sein Verhalten beschrieben. Die Geschichte eines Objekts, die in seinem Zustand festgehalten wird, kann dieses Verhalten beeinflussen. Alle Objekte gleichen Verhaltens sind vom gleichen Datentyp und nur durch ihren Zustand unterschieden. Nur die Abstraktion in Form einer klar definierten Schnittstelle ist von Belang, nicht die konkrete Realisierung (Implementierung).
-
- Beispiel: ein Rechteck, das nach seiner Fläche befragt werden kann
-
- Visual Basic: Dem Verhalten entsprechen Methoden, dem Zustand Felder.
- Vererbung Haben verschiedene Objekttypen Gemeinsamkeiten im Verhalten oder in der Art der Zustände, wäre es ungeschickt, diese doppelt zu realisieren. Sie werden in einem Elternobjekt zusammengefasst, von dem Kindobjekte erben. Durch das Erben übernehmen sie alle Verhaltensweisen und Zustände.
-
- Beispiel: Rechteck und Kreis übernehmen die Flächenauskunft von einer allgemeinen Fläche.
-
- Visual Basic: Deklaration, dass geerbt wird
- Polymorphie (Vielgestaltigkeit) Objekttypen mit Gemeinsamkeiten können unter dem allgemeinen Typ angesprochen werden und berücksichtigen trotzdem ihr spezifisches Verhalten. Dies ist besonders praktisch in Sammlungen von Objekten.
-
- Beispiel: Die Flächenberechnung berücksichtigt die Form (Kreis oder Rechteck), auch wenn nur eine allgemeine Fläche angesprochen wird.
-
- Visual Basic: Deklaration, dass etwas polymorph ist
- Kapselung Programme sollten nicht direkt auf interne Details eines Objekts zugreifen können. Die Sichtbarkeitskontrolle erhöht die Übersichtlichkeit, indem sie unerwartete Änderungen eines Objektzustandes verhindert. Außerdem sorgt die Kapselung dafür, dass ein Programmteil einen anderen nur über klar definierte Schnittstellen beeinflussen kann und daher Änderungen der Implementierung keinen Einfluss auf andere Programmteile oder die sie nutzenden Objekte haben.
-
- Beispiel: Die Geometrie einer Fläche kann nicht direkt geändert werden.
-
- Visual Basic: Sichtbarkeitsmodifikatoren (Public, Protected, Friend, Private)
Damit nicht für jedes Objekt aller Code neu geschrieben werden muss, erlaubt Visual Basic die Definition von Vorlagen (= Klassen) für Objekte. Zum Beispiel reicht es, nur einen Bauplan zu erstellen, um viele ähnliche Autos (= konkrete Objekte) zu bauen. Er spezifiziert den Fahrzeugtyp, zum Beispiel Kombi oder Rennwagen. Dies lässt Spielraum für verschiedene Autos gleichen Typs, zum Beispiel mit verschiedenen Farben. Das konkrete Auto ist eine Instanz einer Klasse, zum Beispiel Kombi, und die Farbe des Autos ist eine Eigenschaft. Ist nicht gerade ein Airbrush-Kunstwerk auf das Auto gespritzt, reicht ein einfaches Feld zum Speichern der Farbe.
3.1.2 Vorteile 

- Modularisierung: Objekte sind in sich abgeschlossene Einheiten. Dadurch lassen sie sich wie Bausteine verwenden. Der größte Baukasten ist das .NET Framework selbst. Damit wird das Zusammensetzen fertiger Bestandteile zu einem wesentlichen Teil des Programmierens.
- Wiederverwendbarkeit: Durch eine einzige Deklaration wird die Funktionalität einer anderen Klasse durch Vererben übernommen, ohne Code selbst schreiben zu müssen.
- Wartungsaufwand: Das Konzept des abstrakten Datentyps und die Kapselung schaffen einfach strukturierte Einheiten, die nur über klar definierte Schnittstellen benutzbar sind. Damit kann eine Klasse unabhängig von ihrer Umgebung getestet werden. Dies bedeutet einen einzelnen Test und nicht einen für jede Kombination.
- Klassen: Die den Objekten eines Typs gemeinsame Funktionalität kann in einer einzigen Klasse zusammengefasst werden und muss nur einmalig erstellt werden.
- Sprachunabhängigkeit: Alle Programmteile, die CLS-konform sind, lassen sich unabhängig von der Programmiersprache kombinieren.
3.1.3 Klassenimplementierung in .NET 

.NET und damit Visual Basic beschränkt die Konzepte der Objektorientierung an wenigen Stellen. Es ist logisch unmöglich, eine Klasse zu definieren, die von sich selbst abhängt (möglich ist das bei generischen Typen, siehe Abschnitt 4.4, »Generisches«). Um erheblichen Problemen in der Praxis aus dem Weg zu gehen, ist es nicht möglich, von mehr als einer Klasse Funktionalität zu übernehmen, nur Einfachvererbung ist erlaubt. Abgemildert wird diese Begrenzung durch die Einführung von Schnittstellen (siehe Abschnitt 3.15, »Schnittstellen: Interface und Implements«). Meiner Erfahrung nach macht die Einschränkung das Leben eines Programmierers sehr viel leichter. Polymorphie ist nicht für Verhalten und Zustände implementiert, die an die Klasse gebunden sind. Noch zwei Bemerkungen zu Namen von Datentypen:
- Es ist unüblich, Datentypen mit einem Buchstaben wie zum Beispiel »C« bei einer Klasse zu kennzeichnen. (Ausnahme: Eine Schnittstelle sollte mit »I« beginnen.)
- Unterstriche in Namen sollten vermieden werden, insbesondere am Namensanfang. (Einige Automatismen generieren Namen mit Unterstrichen.)
3.1.4 Klassen in Visual Basic 

Alle Klassen in Visual Basic folgen demselben Schema. Analog zu den bereits bekannten Modulen besteht eine Klassendefinition aus dem Rahmen (Class X und End Class) und den Klassenmitgliedern. Die beiden wichtigsten Mitglieder sind Felder und Methoden. In den einleitenden Beispielen werden alle Felder öffentlich sein, was durch die Spezifikation von Public erreicht wird (Methoden sind implizit öffentlich). Das folgende Codefragment zeigt das Schema einer Klassendefinition. In diesem Kapitel werden weitere Arten von Klassenmitgliedern und weitere Modifikatoren wie Public vorgestellt, die hier der Einfachheit halber weggelassen sind. Alle kursiv gesetzten Namen müssen Sie Ihren Erfordernissen anpassen.
Class Klassenname |
Der Zugriff auf die Klassenmitglieder erfolgt über eine Punktnotation. Das folgende Codefragment zeigt die Idee des Zugriffs. Wieder können Sie alle kursiv gesetzten Namen Ihren Erfordernissen anpassen. Wie man zu dem Objekt kommt, wird weiter unten erklärt. Hier nur so viel: Es ist vom Typ Klassenname.
Objekt.Feld
Objekt.Prozedur()
3.1.5 Projekttyp »Klassenbibliothek« 

Nach so vielen theoretischen Überlegungen ist es an der Zeit, eine erste konkrete Klasse zu schaffen. Wie in Abschnitt 2.4.2, »Lösungen als Hyperprojekt« beschrieben wurde, fangen wir wieder mit einer leeren Lösung namens Klassendesign an (den Speicherort wählen Sie bitte nach Ihren Erfordernissen). Dieser fügen wir eine Klassenbibliothek hinzu (siehe Abbildung 3.1). Den Dialog rufen Sie über den Menüpunkt Datei • Neu • Projekt im Kontextmenü der Lösung auf.
Abbildung 3.1 Klassenbibliothek
Wir ändern die generierte Klasse
Public Class Class1
End Class
wie folgt ab und passen den Dateinamen an:
' ...\Klassendesign\Auto\Auto.vb |
Option Explicit On
Namespace Klassendesign
Public Class Auto
Public Besitzer As String
End Class
'... wird später ergänzt
End Namespace
Damit haben wir ein Projekt namens Auto mit einer Datei namens Auto.vb. Die erste Zeile schaltet die Typprüfung an (siehe Abschnitt 2.5.2, »Typisierte und untypisierte Variablen (Option Strict)«). Die Definitionen kapseln wir in einem Namensraum, wie in Abschnitt 2.4.1, »Namensräume und Imports«, beschrieben wurde. In diesem Namensraum definieren wir eine Klasse Auto, die durch den Modifikator Public für alle sichtbar ist. Sie enthält ein öffentlich zugängliches Feld Besitzer vom Typ String. Die (öffentlichen) Zustandsvariablen einer Klasse werden auch Eigenschaften genannt. In Abschnitt 3.7, »Eigenschaften«, werden Sie eine besondere Art des Zugriffs auf diese Zustände kennenlernen.
Probieren wir einen ersten Start. Dazu machen wir das Projekt zum Startprojekt (Menüpunkt Als Startprojekt festlegen des Kontextmenüs des Projekts). Ein Start, wie er in Abschnitt 2.3.2, »Start und Test«, beschrieben wurde, führt nur zu einer Fehlermeldung wie in Abbildung 3.2.
Abbildung 3.2 Die Klassenbibliothek kann nicht gestartet werden.
Die Meldung gibt uns bereits Hinweise, was zu tun ist. Als Erstes erstellen wir eine Konsolenanwendung namens Autofahrer (siehe Abschnitt 2.1.2, »Der Projekttyp Konsolenanwendung«). Über ihr Kontextmenü machen wir sie zum Startprojekt. Den Dateinamen und -inhalt ändern wir wie folgt ab und setzen, wie in Abschnitt 2.4.1, »Namensräume und Imports«, beschrieben, den Einstiegspunkt in den Projekteigenschaften (gegebenenfalls muss der Anwendung vertraut werden, siehe Abschnitt 2.3.2, »Start und Test«). Der Grund für die Fehlerbehandlung in einem Try/Catch-Block (siehe Abschnitt 2.8, »Fehlerbehandlung«) wird etwas weiter unten verständlich.
'...\Klassendesign\Autofahrer\Autofahrer.vb |
Option Explicit On
Namespace Klassendesign
Module Autofahrer
Sub Main()
ErstesAuto()
End Sub
Sub ErstesAuto()
Dim Ente As Auto
Try
Console.WriteLine("Besitzer " & Ente.Besitzer)
Catch ex As Exception
Console.WriteLine("Problem: " & ex.Message)
End Try
Console.ReadLine()
End Sub
End Module
End Namespace
Dies ist aber noch nicht alles, denn die Entwicklungsumgebung beschwert sich darüber, dass der Typ Auto nicht bekannt ist. Um das Problem zu beheben, öffnen wir die Projekteigenschaften, zum Beispiel über das Kontextmenü des Projekts. Dort wählen wir das Register Verweise und klicken auf den Button Hinzufügen. In dem Dialog, der nach wenigen Sekunden erscheint, wählen wir, wie Abbildung 3.3 zeigt, auf der Registerkarte Projekte das Projekt Auto (der Dialog ist auch über das Kontextmenü des Projekts erreichbar).
Abbildung 3.3 Referenz hinzufügen
Schließlich sorgen wir dafür, dass der Wurzelnamensraum beider Projekte gleich ist. Er wird auf dem Register Anwendung in den Projekteigenschaften gesetzt. Abbildung 3.4 zeigt ein leeres Feld als eine Möglichkeit.
Abbildung 3.4 Leerer Wurzelnamensraum
Hinweis |
Der Wurzelnamensraum muss in allen zusammenarbeitenden Projekten gleich sein oder Bezeichner müssen mit Wurzel.Namensraum.Bezeichner qualifiziert werden. Als Voreinstellung ist der Wurzelnamensraum gleich dem Projektnamen. |
Je nach Ihrer Konstellation mag noch immer nicht so rechte Freude aufkommen, denn es kann noch einen weiteren Fehler geben:
Assembly '...\Klassendesign\Auto\bin\Debug\Auto.dll' must be strong signed in order to be marked as a prerequisite.
Eine Erklärung oder das Signieren der Bibliothek würde an dieser Stelle den Rahmen der Erläuterungen sprengen. Daher kümmern wir uns hier nur darum, dass die Bibliothek nicht markiert ist. Dazu klicken wir, wie Abbildung 3.5 zeigt, den Button der Anwendungsdateien auf der Registerkarte Veröffentlichen der Projekteigenschaften von Autofahrer.
Abbildung 3.5 Publizierungseinstellungen
Im nun erscheinenden (Abbildung 3.6) Dialog sorgen wir dafür, dass die Bibliothek nicht mehr als Vorraussetzung nötig ist.
Abbildung 3.6 Bibliothek einschließen
Nun wird das Ganze richtig übersetzt, gegebenenfalls über den Menüpunkt Erstellen • Projektmappe neu erstellen.
3.1.6 Bemerkung zu den Codefragmenten 

Um die Beispiele kurz zu halten, ist häufig der Haupteinstiegspunkt weggelassen worden. Der Einstiegspunkt für das Fragment ist jeweils eine parameterlose Prozedur. Bei rein deklarativen Beispielen ist sie weggelassen.
Ein Fragment wie
Option Explicit On
Namespace Klassendesign
...
Module X
Sub Z()
...
End Sub
...
End Module
End Namespace
lautet komplett:
Option Explicit On
Namespace Klassendesign
Module Y
Sub Main()
X.Z()
End Sub
End Module
Module X
Sub Z()
...
End Sub
...
End Module
End Namespace
Die Ellipsen (…) werden nicht immer angegeben, und der Haupteinstiegspunkt kann sich in einer anderen Datei befinden. Die Projekte auf der Buch-DVD sind vollständig.
3.1.7 Objekte durch New 

Damit können wir die Anwendung starten. Die Ausgabe ist enttäuschend:
Problem: Object reference not set to an instance of an object.
Wie alle Variablen wird auch Ente durch die Deklaration Dim Ente As Auto Speicherplatz reserviert und ein Standardwert zugewiesen. Wie in Abschnitt 2.5.6, »Initialisierung von Variablen«, bereits erwähnt wurde, ist dieser Nothing. Ein Zugriff mittels »Nichts« in Ente.Besitzer (=Nothing.Besitzer) muss scheitern und erzeugt die Fehlermeldung. Der Schlüssel zur Lösung des Problems ist die Erzeugung eines Auto-Objekts, das der Variablen zugewiesen wird. In Visual Basic werden Objekte mit dem New-Operator mit folgender Syntax erzeugt (optionale Teile sind in eckige Klammern gesetzt):
New <Klassenname>([<Parameter>]) |
Damit können wir die Autofahrer-Datei anpassen. Im folgenden Codefragment ist auch explizit ein Besitzer angegeben. Ohne diese Zuweisung hätte das Feld Besitzer den Standardwert "" und würde sich in der Ausgabe gar nicht bemerkbar machen.
' ...\Klassendesign\Autofahrer\Autofahrer.vb |
Option Explicit On
Namespace Klassendesign
Module Autofahrer
...
Sub MitBesitzer()
Dim Ente As Auto = New Auto()
Ente.Besitzer = "Kurt Knöffel"
Console.WriteLine("Besitzer " & Ente.Besitzer)
Console.ReadLine()
End Sub
End Module
End Namespace
Es gibt noch eine abkürzende Schreibweise zur Erzeugung eines Objekts bei gleichzeitiger Deklaration und Zuweisung der Referenz an eine Variable:
Dim Ente As New Auto()
Wie bei jeder anderen Variableninitialisierung kann die Initialisierung auch in einer getrennten Zeile erfolgen (siehe Abschnitt 2.5.3, »Variablendeklaration«). Wie dort beschrieben wurde, können auch mehrere Variablen in einer Zeile deklariert werden. Dabei braucht jede Initialisierung ihre eigene As-Klausel.
Dim a1 As Auto = New Auto(), a2 As Auto = New Auto()
Hinweis |
Das Erzeugen eines Objekts vom Typ X wird auch Instanziieren der Klasse X genannt. |
3.1.8 Ausnahmen mit Throw auslösen 

Ein Spezialfall der Objekterzeugung liegt bei der Auslösung einer Ausnahme vor. Im folgenden Codefragment wird die Fläche eines Quadrats interaktiv ermittelt. Durch eine ungültige Länge wird mit Throw eine Ausnahme ausgelöst. Da Throw ein Ausnahmeobjekt erwartet, wird dieses mit New erzeugt. Ob und welche Parameter übergeben werden können, ist durch die Konstruktordefinitionen der Ausnahme festgelegt (siehe Abschnitt 3.13.7, »Eigene Ausnahmen«). Das Catch in der aufrufenden Methode fängt die Ausnahme ab und macht eine entsprechende Ausgabe:
' ...\Klassendesign\Fehlerbehandlung\Werfen.vb |
Option Explicit On
Namespace Klassendesign
Module Werfen
Sub QuadratEingeben()
Dim a As Double
Console.Write("Länge: ")
a = Int32.Parse(Console.ReadLine())
If a <= 0 Then Throw New ArgumentException()
Console.WriteLine("Fläche {0}.", a * a)
End Sub
Sub Auffangen()
Try
QuadratEingeben()
Catch ex As Exception
Console.WriteLine("Ungültig: " & ex.Message)
End Try
Console.ReadLine()
End Sub
End Module
End Namespace
Hinweis |
Throw erfordert ein Objekt von einem Typ, der auf Exception basiert. (Zur Vererbung siehe Abschnitt 3.13.1, »Klassenbeziehung durch Inherits«.) |
Im Fall einer korrekten Länge erfolgt eine Protokollierung der Fläche:
Länge: 7
Fläche 49.
Bei einer ungültigen Eingabe lautet die Ausgabe:
Länge: –2
Ungültig: Value does not fall within the expected range.
Hinweis |
Ausnahmen sollten nicht für den normalen Programmfluss verwendet werden (siehe Abschnitt 2.8, »Fehlerbehandlung«). |
3.1.9 Datentypen 

Referenztypen: Änderungen und Mehrfachreferenzen
Die Anweisung ReferenzVariable = Objekt ist nur anscheinend eine Zuweisung eines Objekts an eine Variable. In Wirklichkeit ist der Wert der Variable nur eine Referenz auf das Objekt und nicht das Objekt selbst. Dieses ist vom System irgendwo auf dem sogenannten Heap (Haufen) abgelegt, der komplett automatisch verwaltet wird. Aus diesem Grund werden .NET-Applikationen auch verwaltete Anwendungen genannt. Alle Datentypen, die nicht direkt Werte speichern, sondern nur Verweise auf diese, werden Referenztypen genannt. Wann immer Sie mit solchen Variablen hantieren, sind nur Verweise auf die eigentlichen Daten im Spiel.
Da verschiedene Verweise auf dieselben Daten möglich sind, können dieselben Daten über verschiedene Namen erreichbar sein. Da alle solchen Variablen auf dieselben (Original-) Daten verweisen, wirken sich alle Änderungen der Daten immer auf identisch dieselben Daten aus. Über welchen der Namen die Daten geändert werden, spielt keine Rolle. Das nächste Codefragment macht dasselbe Auto über zwei Namen erreichbar.
' ...\Klassendesign\Autofahrer\Autofahrer.vb |
Option Explicit On
Namespace Klassendesign
Module Autofahrer
...
Sub Synonyme()
Dim Ente As Auto = New Auto()
Dim ZweiCV As Auto = Ente
Ente.Besitzer = "Kurt Knöffel"
ZweiCV.Besitzer = "Elisa Elbers"
Console.WriteLine("Ente gehört " & Ente.Besitzer)
Console.WriteLine("ZweiCV gehört " & ZweiCV.Besitzer)
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass der Besitzerwechsel nur einmal stattgefunden hat und dass es egal ist, ob man das Vehikel nun »Ente« oder »ZweiCV« nennt.
Ente gehört Elisa Elbers
ZweiCV gehört Elisa Elbers
Hinweis |
Verschiedene Objektreferenzen verweisen nur dann auf verschiedene Objekte, wenn diese mittels New getrennt erzeugt wurden (direkt oder innerhalb einer Funktion). |
Um die Autos unabhängig voneinander zu machen, reicht es also, eine Objekterzeugung für die zweite Referenz einzufügen:
Dim Ente As Auto = New Auto()
Dim ZweiCV As Auto = New Auto()
Hinweis |
Erst, wenn es keine Referenzen mehr auf ein Objekt gibt, wird es freigegeben (zerstört). Die Änderung einer Referenz bei Mehrfachreferenzierung ändert also nichts an der Existenz des Objekts, es bleibt über die anderen Referenzen ansprechbar. |
Werttypen und deren Änderung
Im Gegensatz zu den Referenztypen repräsentieren die Werttypen direkt die Daten. Beispiele für Werttypen sind alle primitiven Datentypen außer String und Object, Strukturen und Enumerationen (beide werden im nächsten Kapitel eingeführt). Werttypen werden nicht auf dem Heap gespeichert, sondern an der Stelle, an der sie verwendet werden. Bei Variablen auf Klassenebene stehen die Werte direkt im Objekt (bei Referenztypen wären es nur Verweise auf diese), während alle lokalen mit Dim gekennzeichneten Variablen einer Methode auf dem sogenannten Stack (Stapel) landen, der alle Daten und Verwaltungsinformationen aller Methoden enthält. Er wird bei Beendigung des Aufrufs automatisch aufgeräumt (vergleiche Abschnitt 2.5.5, »Sichtbarkeit und Lebensdauer«).
Hinweis |
Durch das Speichern von Werttypen »vor Ort« ist bereits zur Kompilierzeit der benötigte Speicherplatz bekannt. Bei Referenztypen, die mal auf dies und dann auf jenes verweisen können, ist das erst zur Laufzeit der Fall, und die Speicherplatzreservierung ist etwas langsamer. |
Damit an den entsprechenden Stellen direkt die Daten vorliegen, werden die Werttypen bei Zuweisungen oder Weitergabe an Methoden immer kopiert. Änderungen an den Daten verändern also nicht das Original. Das folgende Codefragment nutzt solche Werttypen. Die Bedeutung des Typs ist hier nicht von Belang, eigene Definitionen solcher Typen werden im nächsten Kapitel erklärt.
'...\Klassendesign\Werttypen\Werttypen.vb |
Option Explicit On
Option Compare Text
Namespace Klassendesign
Module Werttypen
...
Sub Werttypen()
Dim d1 As System.Collections.DictionaryEntry
d1.Key = "S1" : d1.Value = "V1"
Dim d2 As System.Collections.DictionaryEntry = d1
d2.Key = "S2" : d2.Value = "V2"
Console.WriteLine("d1 = {{{0},{1}}} ",d1.Key,d1.Value)
Console.WriteLine("d2 = {{{0},{1}}} ",d2.Key,d2.Value)
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass beiden Variablen verschiedene Werte enthalten:
d1 = {S1,V1}
d2 = {S2,V2}
Alle primitiven Datentypen sind Beispiele für Werttypen. Aber es gibt noch sehr viel mehr. In den Namensräumen, die mit den Standardeinstellungen in der Anwendung zur Verfügung stehen, sind bereits 132 Werttypen definiert (der Akzent taucht bei generischen Typen auf):
System
ArgIterator, ArraySegment`1, Boolean, Byte, Char, ConsoleKeyInfo, DateTime,
DateTimeOffset, Decimal, Double, Enum, Guid, Int16, Int32, Int64, IntPtr,
ModuleHandle, Nullable`1, RuntimeArgumentHandle, RuntimeFieldHandle,
RuntimeMethodHandle, RuntimeTypeHandle, SByte, Single, TimeSpan,
TypedReference, UInt16, UInt32, UInt64, UIntPtr, Void
System.Threading
AsyncFlowControl, LockCookie, NativeOverlapped
System.Collections.DictionaryEntry
System.Collections.Generic.KeyValuePair`2
System.Diagnostics.SymbolStore.SymbolToken
System.Reflection
CustomAttributeNamedArgument, CustomAttributeTypedArgument,
InterfaceMapping, ParameterModifier
System.Runtime.Serialization
SerializationEntry, StreamingContext
System.Runtime.InteropServices
ArrayWithOffset, BIND_OPTS, BINDPTR, CONNECTDATA, DISPPARAMS, ELEMDESC,
EXCEPINFO, FILETIME, FUNCDESC, GCHandle, HandleRef, IDLDESC, PARAMDESC,
STATSTG, TYPEATTR, TYPEDESC, TYPELIBATTR, VARDESC
System.Runtime.InteropServices.ComTypes
BIND_OPTS, BINDPTR, CONNECTDATA, DISPPARAMS, ELEMDESC, EXCEPINFO, FILETIME,
FORMATETC, FUNCDESC, IDLDESC, PARAMDESC, STATDATA, STATSTG, STGMEDIUM,
TYPEATTR, TYPEDESC, TYPELIBATTR, VARDESC
System.Reflection.Emit
EventToken, FieldToken, Label, MethodToken, OpCode, ParameterToken,
PropertyToken, SignatureToken, StringToken, TypeToken
System.Configuration.Assemblies.AssemblyHash
System.Security.Cryptography
CngProperty, DSAParameters, RSAParameters
System.Windows.Forms
BindingMemberInfo, DataGridCell, LinkArea, Message, Padding,
TableLayoutPanelCellPosition
System.Windows.Forms.VisualStyles.TextMetrics
System.ComponentModel.Design.Serialization
MemberRelationship
System.Collections.Specialized.BitVector32
System.Security.Cryptography.X509Certificates
X509ChainStatus
System.Net.Sockets
IPPacketInformation, SocketInformation
System.IO.WaitForChangedResult
System.Diagnostics.CounterSample
System.Drawing
CharacterRange, Color, Point, PointF, Rectangle, RectangleF, Size, SizeF
System.Data.SqlTypes
SqlBinary, SqlBoolean, SqlByte, SqlDateTime, SqlDecimal, SqlDouble,
SqlGuid, SqlInt16, SqlInt32, SqlInt64, SqlMoney, SqlSingle, SqlString
System.Xml.Serialization.XmlDeserializationEvents
System.Diagnostics.Eventing.EventDescriptor
Microsoft.VisualBasic
SpcInfo, TabInfo
Zusammenfassend lassen sich alle Datentypen in zwei Gruppen einteilen:
- Referenztyp Der Referenztyp ist ein Zeiger auf die Daten.
- Werttyp Der Werttyp enthält die Daten selbst.
Gleichheit
Der Vergleich zweier Variablen kann auf zwei Arten erfolgen:
- Identität: Ein Vergleich auf Identität prüft, ob beide dieselben Daten repräsentieren (ReferenceEquals-Methode oder Operatoren Is und IsNot).
- Gleichheit: Ein Vergleich auf Gleichheit prüft, ob beide Variablen gleichwertig sind (Equals-Methode oder Operatoren = und <>).
Nur Referenztypen können Daten repräsentieren, Werttypen sind keine Repräsentanten, sondern halten die Daten selbst. Daher können nur Referenztypen auf Identität getestet werden. Das folgende Codefragment zeigt einen solchen Vergleich, der mit dem Is-Operator durchgeführt wird.
'...\Klassendesign\Autofahrer\Autofahrer.vb |
Option Explicit On
Namespace Klassendesign
Module Autofahrer
...
Sub Identisch()
Dim Ente As Auto = New Auto()
Dim ZweiCV As Auto = Ente
Console.WriteLine("ZweiCV = Ente " & (ZweiCV Is Ente))
Ente = New Auto()
Console.WriteLine("ZweiCV = Ente " & (ZweiCV Is Ente))
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass die Neuerzeugung des Ente-Objekts die Identität zerstört hat:
ZweiCV = Ente True
ZweiCV = Ente False
Ein weiterer, oft gebrauchter Test ist der auf eine Nullreferenz. Dazu wird statt eines Vergleichsobjekts der Wert Nothing verwendet:
'...\Klassendesign\Autofahrer\Autofahrer.vb |
Option Explicit On
Namespace Klassendesign
Module Autofahrer
...
Sub Null()
Dim Ente As Auto
Console.WriteLine("Ente uninitialisiert " & (Ente Is Nothing))
Console.WriteLine("Ente initialisiert " & (Ente IsNot Nothing))
Console.ReadLine()
End Sub
End Module
End Namespace
Beide Ausgaben zeigen die fehlende Initialisierung:
Ente uninitialisiert True
Ente initialisiert False
Für den Vergleich auf Gleichwertigkeit kommt als Erstes der Operator = in Frage. Er darf bei den primitiven Datentypen (siehe Abschnitt 2.5.4, »Einfache Datentypen«) sowie bei allen Typen verwendet werden , die den Operator definieren (siehe Abschnitt 3.11, »Benutzerdefinierte Operatoren«). Das folgende Codefragment nutzt den Operator zum Vergleich:
'...\Klassendesign\Werttypen\Werttypen.vb |
Option Explicit On
Option Compare Text
Namespace Klassendesign
Module Werttypen
...
Sub PrimitivVergleich()
Dim z1 As Integer = 1, z2 As Integer = 2
Dim s1 As String = "abc", s2 As String = "ABC"
Console.WriteLine("z1 = z2: {0}", z1 = z2)
Console.WriteLine("s1 = s2: {0}", s1 = s2)
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass durch den Schalter Option Compare Text beim Textvergleich Groß- und Kleinschreibung gleichwertig sind.
z1 = z2: False
s1 = s2: True
Alle Datentypen, die den Operator = nicht verwenden dürfen, verwenden die Methode Equals (der Klasse Object) zum Vergleich. Das folgende Codefragment testet zwei Objekte auf Gleichwertigkeit:
'...\Klassendesign\Autofahrer\Autofahrer.vb |
Option Explicit On
Namespace Klassendesign
Module Autofahrer
...
Sub Gleich()
Dim Ente As Auto = New Auto()
Dim ZweiCV As Auto = New Auto()
Ente.Besitzer = "Kurt Knöffel"
ZweiCV.Besitzer = "Kurt Knöffel"
Console.WriteLine("Gleiche Autos " & _
Ente.Equals(ZweiCV))
Console.WriteLine("Gleiche Besitzer: " & _
Ente.Besitzer.Equals(ZweiCV.Besitzer))
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe ist nicht ganz wie erwartet:
Gleiche Autos False
Gleiche Besitzer: True
Die Autos haben keine eigene Definition für Equals und verwenden diejenige von Object. Sie aber hat keine Kenntnis von Auto und kann daher sinnvoll nur auf Identität prüfen. Da es sich um zwei unabhängige Auto-Objekte handelt, ergibt der Vergleich False. Anders sieht es beim Besitzervergleich aus. Die Klasse String hat eine eigene Implementierung der Methode Equals, die den Inhalt (die Zeichenkettenfolge) vergleicht.
Hinweis |
Wenn Equals Objekte inhaltlich vergleichen soll, muss die zugrunde liegende Klasse die Methode Equals neu definieren (siehe Abschnitt 3.14.5, »Equals«) oder den Gleichheitsoperator = überladen (siehe Abschnitt 3.11.3, »Vergleich«). |
String
Der Datentyp String nimmt aus zwei Gründen eine Sonderstellung ein:
- String ist sowohl ein primitiver Datentyp als auch ein Referenztyp.
- Strings sind unveränderbar.
Der erste Punkt sorgt dafür, dass ein Vergleich mit allen drei Möglichkeiten stattfinden kann: Is, = und Equals. Der zweite Punkt erlaubt eine ressourcenschonende Speicherung von String-Literalen. Sie werden nur einmal im Speicher abgelegt, und bei jedem Auftreten eines Literals wird eine Referenz darauf verwendet. Dies kann suggerieren, dass String kein Referenztyp ist. Das folgende Codefragment weist einmal zwei Variablen das gleiche Literal zu und danach eine zusammengesetzte Zeichenkette gleichen Inhalts.
'...\Klassendesign\Autofahrer\Autofahrer.vb |
Option Explicit On
Namespace Klassendesign
Module Autofahrer
...
Sub StringVergleich()
Dim s1, s2 As String
s1 = "ab" : s2 = "ab"
Console.WriteLine("{{s1,s2}}: {{{0},{1}}}", s1, s2)
Console.WriteLine("Is,=: {0},{1}", s1 Is s2, s1 = s2)
Console.WriteLine()
s1 = "a" : s2 = "b"
s1 = s1 & "b" : s2 = "a" & s2
Console.WriteLine("{{s1,s2}}: {{{0},{1}}}", s1, s2)
Console.WriteLine("Is,=: {0},{1}", s1 Is s2, s1 = s2)
Console.ReadLine()
End Sub
End Module
End Namespace
Die erste Ausgabe zeigt, dass beim Verwenden identischer String-Literale nicht nur der Inhalt der Variablen gleich ist, sondern beide auf dasselbe Objekt zeigen (Vergleich mit Is ergibt True). In der zweiten Ausgabe sehen Sie, dass durch das Zusammensetzen von Zeichenketten trotz gleichen Inhalts verschiedene Objekte entstehen können.
{s1,s2}: {ab,ab}
Is,=: True,True
{s1,s2}: {ab,ab}
Is,=: False,True
Hinweis |
Nur String-Literale werden nur einmalig gespeichert (und in einem Pool verwaltet), nicht aber Zeichenketten im Allgemeinen. |
3.1.10 Sichtbarkeit der Klasse 

Bisher haben wir, der Einfachheit halber, alle Definitionen immer möglichst öffentlich gehalten. Dies ist aber kein guter Stil, da es das Prinzip der Kapselung verletzt (siehe Abschnitt 3.1.1, »Einführung«). Wenn eine Klasse Teil einer anderen ist, kann sie dieselben Sichtbarkeitsmodifikatoren haben wie jedes andere Klassenmitglied (siehe Abschnitt 3.2, »Kapselung«). Als alleinstehende Klasse kann sie überall (Public) oder innerhalb der Anwendung (Friend) sichtbar sein (siehe Tabelle 3.1). Eine fehlende Angabe ist gleichbedeutend mit Friend. Diese Bemerkungen gelten auch für andere Datentypen als Klassen.
Modifikator | Beschreibung |
Public |
Eine öffentliche Klasse kann aus jeder beliebigen Anwendung heraus instanziiert werden. |
Friend |
Solche Klassen sind nur innerhalb derselben Anwendung instanziierbar. |
Eine feinere Steuerung der Sichtbarkeit der Klasse selbst ist nicht möglich, sondern muss über die Beschränkung der Klassenmitglieder erfolgen. Verändern wir in der Datei Auto.vb, wie im nächsten Code gezeigt, den Modifikator auf Friend, bekommen wir etliche Fehlermeldungen des Compilers über unerlaubte Zugriffe von Autofahrer.vb, da Friend außerhalb der Anwendung nicht sichtbar ist und sich beide Dateien in verschiedenen Projekten befinden und damit verschiedene Anwendungen darstellen.
'...\Klassendesign\Auto\Auto.vb |
Option Explicit On
Namespace Klassendesign
Friend Class Auto
Public Besitzer As String
End Class
...
End Namespace
Hinweis |
Alleinstehende Klassen dürfen nicht mit Private oder Protected gekennzeichnet werden und sind ohne Kennzeichnung anwendungsweit benutzbar (Friend). |
3.1.11 Aufteilung der Definition mit Partial 

Nur kleine Geister halten Ordnung, das Genie überblickt das Chaos.
Ich weiß nicht, wie Ihnen es geht, aber beim Programmieren bin ich anscheinend ein kleiner Geist, denn Ordnung in meinen Quelltexten hilft mir enorm bei der Arbeit. Je kleiner die Quelltextdateien sind, desto leichter fällt es, den Überblick zu behalten. Visual Basic unterstützt den Programmierer mit der Möglichkeit, die Definition einer Klasse auf mehrere Teile aufzuteilen, die sich in verschiedenen Dateien derselben Anwendung befinden dürfen (nicht müssen). Der Compiler setzt die einzelnen Teile automatisch zusammen, bevor der Quelltext übersetzt wird. Mindestens ein Teil muss mit dem Schlüsselwort Partial gekennzeichnet werden.
Hinweis |
Die Klassenmodifikatoren der Definitionsteile werden kombiniert und müssen konsistent sein. Die Reihenfolge der Definitionsteile einer Klasse ist undefiniert. |
Das folgende Codefragment setzt die Auto-Klasse aus zwei Teilen zusammen. Es zeigt, dass auch leere Teile erlaubt sind. Die Teile dürften auch in verschiedenen Dateien derselben Anwendung stehen.
'...\Klassendesign\Auto\Auto.vb |
Option Explicit On
Namespace Klassendesign
Partial Public Class Auto
Public Besitzer As String
End Class
Partial Public Class Auto
End Class
Partial Public Class Auto
Public Fahrer As String
End Class
End Namespace
Diese Definition ist absolut gleichwertig zu:
Option Explicit On
Namespace Klassendesign
Public Class Auto
Public Besitzer As String
Public Fahrer As String
End Class
End Namespace
Hinweis |
Alle Teile einer Klassendefinition mit mehreren Teilen sollten mit Partial sowie mit den gewünschten Modifikatoren (Sichtbarkeit und Vererbung) gekennzeichnet werden, um die Übersichtlichkeit zu verbessern. |
Die Entwicklungsumgebung zur Erstellung grafischer Benutzeroberflächen macht rege Gebrauch von der Möglichkeit, Klassendefinitionen aufzuteilen.
3.1.12 Grafikbibliothek: Beispiel für Kapitel 3 und 4 

Um nicht jedes Mal wieder von vorn anfangen zu müssen, wird uns in diesem und dem nächsten Kapitel eine kleine Bibliothek begleiten, die wir im Laufe des Textes langsam erstellen. Es geht um grafische Objekte. Da wir vorerst nur Konsolenanwendungen erstellen, werden diese natürlich nicht sauber auf den Bildschirm gezeichnet. Durch das Konzept partieller Klassen kann der Quelltext einer Datei klein gehalten werden. Wir starten mit einer Miniaturklasse in einer Klassenbibliothek (vergleiche Abschnitt 3.1.5, »Projekttyp Klassenbibliothek«). Achten Sie bitte darauf, in den Projekteigenschaften den Wurzelnamensraum zu löschen. Den Inhalt der Datei ändern Sie bitte wie folgt ab (Public wird in Abschnitt 3.2, »Kapselung«, erklärt):
'...\Klassendesign\Graphik\Rechteck.vb |
Option Explicit On
Namespace Klassendesign
Partial Public Class Rechteck
End Class
End Namespace
Um diese Bibliothek zu testen, fügen wir der Lösung noch eine kleine Anwendung (Projekt) namens Zeichner hinzu. Wie in Abschnitt 2.4.1, »Namensräume und Imports«, beschrieben, setzen wir den Einstiegspunkt. Wenn die Dateien über ein Netzwerk erreicht werden, vertrauen wir der Anwendung in den Sicherheitseinstellungen (siehe Abschnitt 2.3.2, »Start und Test«). Damit die Grafikbibliothek nutzbar ist, referenzieren wir sie noch und löschen den Wurzelnamensraum, wie in Abschnitt 3.1.5, »Projekttyp Klassenbibliothek«, beschrieben (dort steht auch, wie man gegebenenfalls mit einem Fehler bezüglich »prerequisite« umgeht). Schließlich machen wir die Anwendung über ihr Kontextmenü zum Startprojekt. Der Inhalt der Anwendungsdatei ist wie folgt (die Bedeutung von Shared ist hier nicht wichtig, siehe Abschnitt 3.4, »Bindung«; es wurde Class statt Module gewählt, um die Definitionen mit Partial erweitern zu können).
'...\Klassendesign\Zeichner\Zeichner.vb |
Option Explicit On
Namespace Klassendesign
Partial Class Zeichner
Shared Sub Main()
Dim z As Zeichner = New Zeichner()
z.RechteckTest()
...
Console.ReadLine()
End Sub
Sub RechteckTest()
Dim kr As Rechteck = New Rechteck()
Console.WriteLine("Erledigt. ")
End Sub
End Class
End Namespace
In diesem und dem nächsten Kapitel werden wir die Grafikbibliothek sukzessive erweitern und Testroutinen zur Zeichner-Anwendung hinzufügen. Die erste Erweiterung erfolgt in Abschnitt 3.2.6, »Grafikbibliothek: private Größe des Rechtecks«.
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.