4.2 Strukturen 

Alle Objekte, die als Datentyp eine Klasse haben, nutzen eine Referenz auf das Objekt für den Zugriff auf das Objekt. Bei einer Weitergabe des Objekts wird nur die Referenz und nicht das Objekt selbst weitergegeben. Demgegenüber haben Sie bei Werttypen das Objekt selbst »in der Hand«. Damit nun das Originalobjekt nicht »verschwindet«, wird bei einer Weitergabe jedes Mal eine Kopie angefertigt. Dieser Unterschied zwischen Referenz- und Werttypen ist in Abschnitt 3.1.9, »Datentypen«, näher beschrieben. Mit den Strukturen haben Sie zum ersten Mal die Möglichkeit, selbst Werttypen zu definieren. Sie haben folgende Syntax (optionale Teile stehen in eckigen Klammern, kursiv gesetzte Teile müssen Sie Ihren Erfordernissen anpassen, zur Typen-Spezifikation siehe Abschnitt 4.4, »Generisches«):
[<Modifikatoren>] Structure Struktur[Typen] |
Eine Struktur ist bis auf das Wort Structure statt Class identisch mit einer Klasse (inklusive der Aufteilbarkeit mit Partial), außer dass sie
- nie mit Inherits die Elternklasse angibt, sondern implizit von ValueType erbt (konsequenterweise ist die Verwendung von MyBase verboten),
- nie beerbt werden kann, sondern immer implizit NotInheritable ist,
- als Modifikatoren nur Partial, Public und Friend erlaubt – sowie Shadows, Private und Protected für Strukturen als Teil anderer Datentypen,
- mindestens eine Variable oder ein Ereignis deklarieren muss,
- nie benutzerdefinierte parameterlose Objektkonstruktoren hat (ein mit Shared deklarierter Klassenkonstruktor ist erlaubt),
- den parameterlosen Konstruktor nicht durch benutzerdefinierte Konstruktoren verliert,
- nie objektgebundene Strukturvariablen innerhalb der Deklaration initialisiert (mit Shared deklarierte klassengebundene Variablen dürfen initialisiert werden) und
- für Strukturvariablen WithEvents nicht spezifizieren darf und auch nicht als Datentyp für eine beliebige Variable benutzt werden darf, die WithEvents spezifiziert.
Die folgenden Codefragmente sind daher alle fehlerhaft:
'Compilerfehler: Struktur enthält weder eine Variable noch ein Ereignis!!
Structure Struktur
End Structure
'Compilerfehler: Angabe einer Elternklasse!!
Structure Struktur Inherits ValueType
Dim var As Single
End Structure
'Compilerfehler Struktur: Strukturen sind NotInheritable!!
Structure Basis : Dim var As Single : End Structure
Structure Struktur : Inherits Basis : Dim var As Single : End Structure
Class Klasse : Inherits Basis : End Class
Structure Struktur
Dim var As Single = 0 'Compilerfehler: Initialisierung!!
Sub New() 'Compilerfehler: parameterlos ohne Shared!!
End Sub
WithEvents x As String 'Compilerfehler: WithEvents!!
End Structure
Als erstes Beispiel schauen wir uns die Definition einer Person an. Es fällt auf, dass eine Objekterzeugung mit New fehlt. Da eine Struktur ein Werttyp ist, werden ihre Daten direkt in dem Block gespeichert, in dem die Strukturvariable deklariert wird, und nicht auf dem blockunabhängigen Heap, der alle Referenztypen speichert. Die Struktur enthält auch eine Methode Info(), um darauf aufmerksam zu machen, dass Strukturen nicht auf Variablen und Ereignisse beschränkt sind. Der Zugriff in Test() unterscheidet sich formal nicht von dem, den Objekte einer Klasse nutzen.
'...\Datentypen\Strukturen\Prinzip.vb |
Option Strict On
Namespace Datentypen
Structure Person
Friend Name As String
Sub Info()
Console.WriteLine("Name: {0}", Name)
End Sub
End Structure
Module Prinzip
Sub Test()
Dim p As Person 'kein New!!
p.Info()
p.Name = "Winston Smith"
p.Info()
Console.ReadLine()
End Sub
End Module
End Namespace
Die erste Ausgabe zeigt, dass der durch die Deklaration implizit aufgerufene parameterlose Konstruktor keinen Einfluss auf die Strukturvariable hat. Sie ist mit einem Leerstring "" belegt. Andere Datentypen erhalten analog eine datentypspezifische »Null« (siehe Abschnitt 2.5.6, »Initialisierung von Variablen«). Nach der Zuweisung eines Namens wird dieser in der zweiten Ausgabe verwendet.
Name:
Name: Winston Smith
Hinweis |
Ganz neue Werttypen analog zu Strukturen können nicht von Ihnen geschaffen werden. |
4.2.1 Vor- und Nachteile von Referenz- und Werttypen 

Klassen und Strukturen unterscheiden sich im Wesentlichen darin, wo die Daten eines Objekts gespeichert werden. Erstere sind Referenztypen, und Variablen dieses Typs halten nur einen Verweis auf die eigentlichen Daten, während bei Letzteren die Daten direkt in der Objektvariablen gespeichert sind. Wird eine Variable an eine Methode weitergegeben oder einer anderen Variablen zugewiesen, erfolgt bei einem Referenztyp der Zugriff immer auf die Originaldaten, während bei Werttypen mit einer Kopie der Daten gearbeitet wird. Zusammengefasst hat ein Werttyp folgende Charakteristika:
- Vorteile: Originaldaten bleiben erhalten, Objektkopien erfolgen implizit durch Weitergabe.
- Nachteile: Originaldaten können nicht geändert werden, Kopien verbrauchen Ressourcen.
Um den Kopiermechanismus zu veranschaulichen, zeigt das nächste Codefragment die Veränderung einer Struktur namens Glasscheibe in einer Methode Verkauf(), die die Struktur als Parameter entgegennimmt und die Mitgliedsvariable Preis ändert. Die Änderung der Struktur selbst ist erforderlich, da die Methode Info() die Struktur selbst als einzige Informationsquelle nutzt. Ob das ein glückliches Design darstellt, sei dahingestellt, hier kommt es nur auf den Mechanismus des Kopierens an.
'...\Datentypen\Strukturen\Kopien.vb |
Option Strict On
Namespace Datentypen
Structure Glasscheibe
Public Preis As Double
Public Farbe As String
Sub Info()
Console.WriteLine("{0}es Glas kostet {1}", Farbe, Preis)
End Sub
End Structure
Module Kopien
Sub Verkauf(ByVal glas As Glasscheibe)
glas.Preis *= 1.19
glas.Info()
End Sub
Sub Test()
Dim rot As Glasscheibe : rot.Farbe = "Rot" : rot.Preis = 172
Verkauf(rot) : Verkauf(rot)
Dim blau As Glasscheibe = rot : blau.Farbe = "Blau" : blau.Preis = 124
Verkauf(rot) : Verkauf(blau)
Console.ReadLine()
End Sub
End Module
End Namespace
Die ersten beiden Ausgaben zeigen, dass die Änderung der Strukturvariablen in der Methode Verkauf() sich trotz des zweimaligen Aufrufs nicht auf das Original auswirkt und daher immer der korrekte Preis ausgegeben wird (und nicht durch jeden Verkauf erhöht wird). Auch bei der Zuweisung der Variablen blau wird keine zweite Referenz auf dieselben Daten erzeugt, sondern die Daten werden kopiert, wie die gegenüber den ersten Zeilen unveränderte Ausgabe in der dritten Zeile zeigt:
Rotes Glas kostet 204.68
Rotes Glas kostet 204.68
Rotes Glas kostet 204.68
Blaues Glas kostet 147.56
Hinweis |
Bei Werttypen gibt es nie zwei Referenzen auf dieselben Daten. |
4.2.2 Initialisierung und Konstruktoren 

Strukturen haben natürlich auch die Möglichkeit, Konstruktoren zu deren Initialisierung zu definieren. Anders als bei Klassen muss jeder selbst definierte Konstruktor Parameter besitzen, wobei der parameterlose Standardkonstruktor in jeder Situation zur Verfügung gestellt wird (und gar nicht selbst definiert werden kann). Damit geht einher, dass es unmöglich ist, durch private Konstruktoren die Instanziierung einer Struktur komplett zu kontrollieren oder gar zu verhindern. Im Kontext automatisch erstellter Kopien bei der Weitergabe oder Zuweisung ist eine solche Beschränkung sinnvoll. Da die Initialisierung von Variablen in einer Struktur während der Deklaration nicht erlaubt ist, sind benutzerdefinierte Konstruktoren selbst dann sinnvoll, wenn die Struktur mit den immer gleichen Werten ungleich »Null« starten soll. In einem solchen Fall müssen Sie halt mit einem Dummy-Parameter im Konstruktor arbeiten, da Ihnen der parameterlose Konstruktor ja verwehrt ist.
Das nächste Codefragment definiert eine Struktur zur Speicherung eines Punktes mit zwei benutzerdefinierten Konstruktoren, wobei der zweite zufällige Werte für x und y nimmt und die z-Koordinate nicht initialisiert, sondern bei 0 belässt (im Gegensatz zu C# müssen in Visual Basic nicht alle verwendeten Strukturvariablen explizit initialisiert werden). Da die Koordinatenvariablen mit Private deklariert sind, kann ein einmal erzeugter Punkt nicht mehr geändert werden. Die überschriebene Methode ToString() wird »lesbare« Ausgaben produzieren.
'...\Datentypen\Strukturen\Konstruktoren.vb |
Option Strict On
Namespace Datentypen
Structure Punkt
Private x, y, z As Double
Sub New(ByVal x As Double, ByVal y As Double, _
Optional ByVal z As Double = z0) 'z0 siehe unten
Me.x = x : Me.y = y : Me.z = z
End Sub
Sub New(ByVal start As Integer)
Dim rand As Random = New Random(start)
x = rand.NextDouble() : y = rand.NextDouble()
End Sub
Public Overrides Function ToString() As String
Return "{" & x.ToString("0.##") & "," & y.ToString("0.##") & _
"," & z.ToString("0.##") & "}"
End Function
End Structure
...
End Namespace
Zum Test werden Tetraederpunkte mit zwei und drei Koordinaten erzeugt und in der Methode Ausgabe() protokolliert, die ein Array von Punkten erwartet. Analog werden Dreieckspunkte mit einem Parameter generiert und ausgegeben. Schließlich wird bei der Variablen null ganz auf Parameter verzichtet.
'...\Datentypen\Strukturen\Konstruktoren.vb |
Option Strict On
Namespace Datentypen
...
Module Konstruktoren
Public Const z0 As Double = –0.20412414523193151 '-1/(2*Math.Sqrt(6))
Sub Ausgabe(ByVal was As String, ByVal p As Punkt())
Console.Write("{0}: |", was)
For Each pt As Punkt In p : Console.Write("{0}|", pt) : Next
Console.WriteLine()
End Sub
Sub Test()
Dim x As Double = 1 / (2 * Math.Sqrt(3))
Dim tetraeder As Punkt() = {New Punkt(0, 0, z0 + Math.Sqrt(2 / 3)), _
New Punkt(-x, –1 / 2), New Punkt(-x, 1 / 2), New Punkt(2 * x, 0)}
Ausgabe("Tetraeder", tetraeder)
Dim dreieck As Punkt() = {New Punkt(0), New Punkt(1), New Punkt(2)}
Ausgabe("Dreieck", dreieck)
Dim null As Punkt
Console.WriteLine("Nullpunkt: {0}", null)
null = New Punkt()
Console.WriteLine("Nullpunkt: {0}", null)
Console.ReadLine()
End Sub
End Module
End Namespace
Die erste Ausgabe bestätigt die Verwendung des dreiparametrigen Konstruktors unter Verwendung des optionalen z-Wertes. Das Dreieck verwendet den einparametrigen Konstruktor mit zufälligen Koordinaten. Die letzten beiden Ausgaben dokumentieren den impliziten und den expliziten Einsatz des immer vorhandenen parameterlosen Konstruktors.
Tetraeder: |{0,0,0.61}|{-0.29,-0.5,-0.2}|{-0.29,0.5,-0.2}|{0.58,0,-0.2}|
Dreieck: |{0.73,0.82,0}|{0.25,0.11,0}|{0.77,0.4,0}|
Nullpunkt: {0,0,0}
Nullpunkt: {0,0,0}
4.2.3 Boxing 

Jedes Objekt ist (unter anderem) vom Typ Object und – beim Einsatz einer Implements-Klausel – vom entsprechenden Schnittstellentyp. Da beide Referenztypen sind, kann die Situation auftreten, dass eine Referenz auf eine Struktur gebraucht wird. Damit die Handhabung einfach bleibt, wird die hierfür notwendige Funktionalität, inklusive des Kopierens, vom Compiler erzeugt bzw. von der Laufzeitumgebung bereitgestellt. Der Mechanismus wird Boxing genannt, da ein Werttyp in einen Referenztyp verpackt wird. Der umgekehrte Weg heißt Unboxing.
Das nächste Codefragment prüft, ob durch das Boxing und Unboxing durch CType eine Kopie der Struktur erstellt wird. Dazu wird durch Zuweisung an eine Variable vom Typ Object eine Referenz auf eine Struktur erzeugt. Danach wird die Struktur geändert, und es werden sowohl die Struktur als auch die Referenz ausgegeben. Analog wird die Referenz an die Strukturvariable zugewiesen, Letztere wird geändert, und beide werden ausgegeben. Dabei muss das Auslesen der Referenz vom Typ Object über Reflection erfolgen, da sie sonst nur über eine Typumwandlung auszulesen wäre und es ja gerade darum geht, ob eine solche Umwandlung eine Kopie erzeugt.
'...\Datentypen\Strukturen\Boxing.vb |
Option Strict On
Namespace Datentypen
Structure Schauspieler
Public Name As String
Sub New(ByVal name As String)
Me.Name = name
End Sub
End Structure
Module Boxing
Sub Test()
Dim person As New Schauspieler("Marion Robert Morrison")
Dim ref As Object = person
Dim name As Reflection.FieldInfo = ref.GetType().GetField("Name")
person.Name = "Marion Michael Morrison"
Console.WriteLine("Schauspieler {0}", person.Name)
Console.WriteLine("Referenz {0}", name.GetValue(ref))
person = CType(ref, Schauspieler)
person.Name = "The Duke"
Console.WriteLine("Schauspieler {0}", person.Name)
Console.WriteLine("Referenz {0}", name.GetValue(ref))
Console.ReadLine()
End Sub
End Module
End Namespace
Der Vergleich der ersten beiden und der letzten beiden Ausgaben zeigt jeweils, dass sowohl beim Boxing als auch beim Unboxing das Original erhalten bleibt und eine Kopie der Struktur erstellt wird.
Schauspieler Marion Michael Morrison
Referenz Marion Robert Morrison
Schauspieler The Duke
Referenz Marion Robert Morrison
Hinweis |
Wie jede andere Art der Konvertierung greifen die Boxing-Mechanismen wenn nötig auf implizite Konvertierungen zurück. |
Hinweis |
Wenn eine Struktur eine Schnittstelle implementiert und als Letztere angesprochen wird, dann findet ein Boxing statt, da jede Schnittstelle ein Referenztyp ist. |
4.2.4 Innere Strukturen 

Wie Klassen können Strukturen sowohl andere Datentypen enthalten als auch Teil anderer Datentypen sein. Deklaration und Zugriff funktionieren wie bei Klassen (siehe Abschnitt 3.8, »Innere Klassen«). Eine Besonderheit gibt es aber: Da Strukturen Werttypen sind, wird bei der Weitergabe oder Zuweisung eine Kopie der Struktur erstellt. Wie wird da mit geschachtelten Datentypen verfahren?
Im folgenden Codefragment enthält die Struktur Regal eine Referenzvariable box, deren Klasse die Variable Buchnummer zur Identifikation enthält. In der Methode Test() wird die Struktur instanziiert und danach zur Erstellung einer automatischen Kopie einer anderen Variablen zugewiesen. Schließlich wird die Buchnummer in der ersten Struktur abgeändert, und beide Strukturen werden ausgegeben.
'...\Datentypen\Strukturen\FlacheKopie.vb |
Option Strict On
Namespace Datentypen
Class Schachtel
Public Buchnummer As Integer
Sub New(ByVal nummer As Integer)
Buchnummer = nummer
End Sub
End Class
Structure Regal
Public box As Schachtel
Sub New(ByVal nummer As Integer)
box = New Schachtel(nummer)
End Sub
End Structure
Module FlacheKopie
Sub Test()
Dim Wohnzimmer As New Regal(27)
Dim Schlafzimmer As Regal = Wohnzimmer
Wohnzimmer.box.Buchnummer = 12
Console.WriteLine("Wohnzimmer {0}", Wohnzimmer.box.Buchnummer)
Console.WriteLine("Schlafzimmer {0}", Schlafzimmer.box.Buchnummer)
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass die Änderung der Nummer in der einen Struktur auch die andere Struktur betrifft. Das, worauf die Variable box verweist, ist also nicht kopiert worden.
Wohnzimmer 12
Schlafzimmer 12
Damit lässt sich festhalten:
Kopien von Werttypen sind flach, das heißt, die Daten von Referenzen werden nicht kopiert, sondern nur die Referenz selbst. |
4.2.5 ValueType 

Durch die Deklaration einer Struktur wird implizit eine gleichnamige Klasse identischen Inhalts erzeugt, die auf der Klasse ValueType basiert. Sie dürfen diese Vererbungsbeziehung nicht herstellen, dies bleibt dem Compiler vorbehalten. Die Deklaration
Structure Struktur
Public Variable As Single
End Structure
entspricht folgender Situation (fiktiv in Bezug auf die Darstellung der Struktur als Klasse):
Public NotInheritable Class Struktur Inherits ValueType
Public Variable As Single
End Struktur
Public MustInherit Class ValueType Inherits Object
Public Overrides Function GetHashCode() As Integer
Public Overrides Function Equals(ByVal obj As Object) As Boolean
Public Overrides Function ToString() As String
Private Shared Function CanCompareBits(ByVal obj As Object) As Boolean
Private Shared Function _
FastEqualsCheck(ByVal a As Object, ByVal b As Object) As Boolean
End Class
Daraus können Sie erkennen, dass die Elternklasse aller Werttypen selbst ein Referenztyp ist (so merkwürdig das auch scheinen mag).
Hinweis |
Neben Strukturen zählen noch die Primitiven (außer Strings) und Enumerationen zu den Werttypen. |
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.