4.3 Enumerationen
»Weniger ist mehr.« (Ludwig Mies van der Rohe)
Wenn eine Variable nur eine begrenzte Anzahl an Werten annehmen kann, sollte verhindert werden, dass »falsche« Werte zugewiesen werden können. Eine Prüfung vor jeder Neubelegung ist sehr unkomfortabel und fehlerträchtig. Besser ist es, wenn aufgrund des Datentyps der Variablen lediglich erlaubte Werte zugewiesen werden können. Ein erster Ansatz dazu stützt sich auf die Definition einer Klasse.
Class Farbe Public Const Kreuz As Integer = 3 Public Const Pik As Integer = 2 Public Const Herz As Integer = 1 Public Const Karo As Integer = 0 End Class
Wenn Sie nun eine Variable vom Typ Farbe definieren, können Sie auf diese Konstanten zugreifen. Obwohl das das Problem im Prinzip löst, ist es keine ideale Lösung. Einerseits ist es relativ viel Tipparbeit für ein paar Werte, und andererseits fehlen alle Funktionen, die mit den Konstanten zusammenhängen, wie etwa die Repräsentation als Zeichenkette oder die Aufzählung aller erlaubten Konstanten. Außerdem wird nicht verhindert, dass inkompatible oder bezüglich eines Vergleichs ungeeignete Typen verwendet werden (zum Vergleichsproblem siehe den Unterabschnitt »Fließkommazahlen als Zähler« in Abschnitt 2.10.2, »For«). Daher bietet Visual Basic einen Datentyp, der sich auf das Wesentliche beschränkt, mit folgender Syntax (optionale Teile stehen in eckigen Klammern, kursiv gesetzte Teile müssen Sie Ihren Erfordernissen anpassen):
[<Modifikatoren>] Enum Enumeration [As Typ] Name [ = Konstante] [<weitere Werte/Initialisierungen>] End Enum |
Dabei sind noch ein paar Besonderheiten zu beachten:
- Als Modifikatoren sind nur Public und Friend erlaubt sowie Shadows, Private und Protected für Enumerationen als Teil anderer Datentypen.
- Typ darf nur einer der ganzzahligen Primitiven Byte, SByte, UShort, UInt16, Short, Int16, UInteger, UInt32, Integer, Int32, ULong, UInt64, Long oder Int64 sein. Ohne Angabe der As-Klausel ist es Integer.
- Jedes Mitglied steht in einer eigenen logischen Zeile (logische Zeilen können mit einem Doppelpunkt zu einer Codezeile zusammengefasst werden, siehe Abschnitt 2.2.4, »Anweisungen«).
- Mitglieder einer Enumeration haben keinerlei Modifikatoren, sondern sie sind implizit Public Const und werden damit über den Klassennamen angesprochen.
- Name muss innerhalb der Enumeration eindeutig sein.
- Konstanten müssen zur Compilezeit bekannt sein und zu Typ passen.
- Mitglieder ohne Initialisierung sind um eins größer als ihre Vorgänger, wobei eine fehlende Initialisierung des ersten Mitglieds durch 0 ersetzt wird.
- Mitglieder können nur als Konstanten nur für nachfolgende Mitglieder verwendet werden.
- Eine Konstante darf kleiner sein als das vorherige Mitglied.
- Eumerationen können weder mit Implements eine Schnittstelle implementieren noch mit Inherits eine Elternklasse festlegen.
- Andere Arten von Mitgliedern sind verboten, auch ein Klassenkonstruktor.
- Wie Primitive sind Enumerationswerte unveränderlich.
Als Enumeration ist obige Klasse kürzer formulierbar:
Enum Farbe Karo Herz Pik Kreuz End Enum
Oder noch kürzer (auch wenn Visual Studio beim Tippen immer wieder versucht, End Enum einzufügen):
Enum Farbe : Karo : Herz : Pik : Kreuz : End Enum
Das folgende Codefragment definiert dieselbe Enumeration auf eine etwas andere Art und gibt in einer Schleife die Werte aus – zusammen mit einem Vergleich mit dem festen Wert pik. Zum Test enthält die Enumeration drei Mitglieder mit gleichem Wert.
'...\Datentypen\Enumerationen\Prinzip.vb |
Option Strict On Namespace Datentypen Enum Farbe Karo Pik = 2 Kreuz Herz = Karo + 1 Eichel = Kreuz Treff = Kreuz End Enum Module Prinzip Sub Test() Dim pik As Farbe = Farbe.Pik For Each f As Farbe In [Enum].GetValues(GetType(Farbe)) Console.WriteLine("Farbe {0} mit Wert {1} gleich Pik: {2}", _ f, CType(f, Integer), pik = f) Next Console.ReadLine() End Sub End Module End Namespace
Die von WriteLine implizit aufgerufene Methode ToString() erzeugt die von uns spezifizierten Namen. Bei mehrdeutigen Werten wird der bezüglich des aus dem Namen gebildeten Hashwerts erste Name genommen, hier der Name Eichel. Die standardmäßige Ausgabe von Namen statt Werten ist der Grund für die Typumwandlung mit CType. Durch sie wird ein String von der ganzen Zahl und nicht dem Enumerationsmitglied erzeugt.
Farbe Karo mit Wert 0 gleich Pik: False Farbe Herz mit Wert 1 gleich Pik: False Farbe Pik mit Wert 2 gleich Pik: True Farbe Eichel mit Wert 3 gleich Pik: False Farbe Eichel mit Wert 3 gleich Pik: False Farbe Eichel mit Wert 3 gleich Pik: False
Hinweis |
Wie alle Werttypen wird keine Referenz erzeugt, sondern die Werte werden direkt in dem Block gespeichert, in dem die Enumerationsvariable deklariert ist. Zu den sich daraus ergebenden Effekten vergleiche Abschnitt 4.2.1, »Vor- und Nachteile von Referenz- und Werttypen«, und Abschnitt 4.2.3, »Boxing«. |
4.3.1 Datentyp
Wenn Sie für eine kleinere Anzahl Konstanten nicht 4 Byte einer Integer-Variablen verbrauchen möchten oder die Werte der Konstanten nicht zu Integer passen, können Sie den Datentyp der Enumeration explizit festlegen. Das folgende Codefragment zeigt dazu zwei Enumerationen mit unterschiedlichem ganzzahligem Typ. Die Werte der ersten sind so gewählt, dass sie jeweils nur ein Bit setzen und als Flags dienen können, die ohne Informationsverlust kombiniert werden können. In der Methode Test() wird mit den Enumerationswerten gerechnet, und sie werden wie jede andere ganze Zahl konvertiert:
'...\Datentypen\Enumerationen\Datentyp.vb |
Option Strict On Namespace Datentypen Enum Font As Byte Kursiv = 1 Fett = 2 Hervorgeboben = Kursiv Or Fett Unterstrichen = 4 End Enum Enum Einheit As Long Meter = 1 Erddurchmesser = 12756000 AstronomischeEinheit = 149597870691L Parsec = 30856775812743679L End Enum Module Datentyp Sub Test() Dim art As Font = Font.Unterstrichen Or Font.Kursiv Dim typ As Integer = art Console.Write("Die Schrift ist ") For Each f As Font In [Enum].GetValues(GetType(Font)) If (typ And f) = f Then Console.Write(f.ToString() & " ") Next Console.WriteLine("({0}, nicht {1})", art, Font.Fett Or Font.Kursiv) Console.WriteLine("{0} Erddurchmesser bis zur Sonne", _ Einheit.AstronomischeEinheit \ Einheit.Erddurchmesser) Console.WriteLine("Nächste Stern: {0}-mal weiter weg als die Sonne", _ CType(1.33 * Einheit.Parsec / Einheit.AstronomischeEinheit, Integer)) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe bestätigt die korrekte Arbeitsweise. Außerdem ist in der ersten Zeile zu sehen, dass bei einer Kombination von Enumerationswerten die automatische Konvertierung zu den Namen in der Enumeration verloren geht, wenn für den Zahlenwert der Kombination kein Name in der Enumeration vergeben ist .
Die Schrift ist Kursiv Unterstrichen (5, nicht Hervorgeboben) 11727 Erddurchmesser bis zur Sonne Nächste Stern: 274332-mal weiter weg als die Sonne
Enumerationen können auch als Datentyp für Methodenparameter dienen. Das nächste Beispiel nutzt eine Enumeration als Parameter der Methode Info(), da es nur exakt 5 Körper gibt, die von gleichen regelmäßigen Vielecken begrenzt sind.
'...\Datentypen\Enumerationen\Parameter.vb |
Option Strict On Namespace Datentypen Enum Platonisch : Tetraeder : Würfel : Oktaeder : Dodekaeder : Ikosaeder End Enum Module Parameter Sub Info(ByVal körper As Platonisch) Dim f As String = "{0,10}: {1,2} Ecken, {2,2} Kanten, {3,2} Flächen" Select Case körper Case Platonisch.Tetraeder : Console.WriteLine(f, körper, 4, 6, 4) Case Platonisch.Würfel : Console.WriteLine(f, körper, 8, 12, 6) Case Platonisch.Oktaeder : Console.WriteLine(f, körper, 6, 12, 8) Case Platonisch.Dodekaeder : Console.WriteLine(f,körper, 20, 30, 12) Case Platonisch.Ikosaeder : Console.WriteLine(f, körper, 12, 30, 20) End Select End Sub Sub Test() For Each p As Platonisch In [Enum].GetValues(GetType(Platonisch)) Info(p) Next Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt einige Eigenschaften aller vollständig regelmäßigen Körper:
Tetraeder: 4 Ecken, 6 Kanten, 4 Flächen Würfel: 8 Ecken, 12 Kanten, 6 Flächen Oktaeder: 6 Ecken, 12 Kanten, 8 Flächen Dodekaeder: 20 Ecken, 30 Kanten, 12 Flächen Ikosaeder: 12 Ecken, 30 Kanten, 20 Flächen
4.3.2 System.Enum
Alle Enumerationen sind von System.Enum abgeleitet. Dies wird vom Compiler erledigt, Sie dürfen eine entsprechende Inherits-Klausel nicht angeben. Nehmen wir ein einfaches Beispiel:
Enum Enumeration As Long Mitglied = 17 End Enum
Der Compiler passt die Enumeration an und stellt die Vererbungsbeziehung her. Im folgenden Listing sind die Signaturen abgedruckt. System.Enum ist von System.ValueType abgeleitet, das wiederum in Abschnitt 4.2.5, »ValueType«, abgedruckt ist. Als obsolet markierte Mitglieder sind in spitze Klammern gesetzt, vor implementierenden Methoden steht, durch einen Punkt getrennt, die Schnittstelle. Um die Ausgabe nicht zu lang werden zu lassen, sind alle ByVal-Modifikatoren von Methodenparametern sowie die Modifikatoren NotOverridable, Overrides und Overloads ausgelassen. Optionale Parameter sind kursiv gesetzt. Der Name GANZ steht für Byte, SByte, UShort, UInt16, Short, Int16, UInteger, UInt32, Integer, Int32, ULong, UInt64, Long oder Int64, PRIM darf zusätzlich für Boolean, Char, Single, Double oder DateTime stehen. Der Parameter ET ist der Datentyp der Enumeration, W der betrachtete Wert. Die privaten Mitglieder sind der Vollständigkeit halber abgedruckt und nur über Klassen aus dem Namensraum Reflection verwendbar.
Public NotInheritable Class Enumeration : Inherits Enum Public value__ As Long Public Shared Const Mitglied As Enumeration End Class Public MustInherit Class Enum Inherits ValueType 'Public Function Equals(obj As Object) As Boolean Function GetHashCode() As Integer Function ToString() As String Function ToString(format As String) As String Function <ToString>(format As String, konv As IFormatProvider) As String Function IComparable.CompareTo(mit As Object) As Integer Function IConvertible.GetTypeCode() As TypeCode Shared Function Parse(ET As Type, W As String, egal As Boolean) As Object Shared Function GetUnderlyingType(ET As Type) As Type Shared Function GetValues(ET As Type) As Array Shared Function GetName(ET As Type, W As Object) As String Shared Function GetNames(ET As Type) As String() Shared Function IsDefined(ET As Type, W As Object) As Boolean Shared Function Format(ET As Type, W As Object, form As String) As String Shared Function ToObject(ET As Type, W As Object) As Object Shared Function ToObject(ET As Type, W As GANZ) As Object 'Private Function IConvertible.ToPRIM(konv As IFormatProvider) As PRIM Function Iconvertible.ToType(ET As Type,konv As IFormatProvider) As Object Shared Function InternalCompareTo(o1 As Object, o2 As Object) As Integer Shared Function InternalGetUnderlyingType(ET As Type) As Type Shared Sub InternalGetEnumValues( _ ET As Type, ByRef werte As ULong(), ByRef namen As String()) Shared Function InternalBoxEnum(ET As Type, W As Long) As Object Function InternalGetValue() As Object Shared Function GetValueField(ET As Type) As FieldInfo Shared Function GetHashEntry(ET As Type) As HashEntry Shared Function InternalGetValueAsString(ET As Type,W As Object) As String Shared Function InternalFormattedHexString(W As Object) As String Shared Function InternalFormat(ET As Type, W As Object) As String Shared Function InternalFlagsFormat(ET As Type, W As Object) As String Shared Function ToUInt64(W As Object) As ULong Shared Function BinarySearch(array As ULong(), W As ULong) As Integer Function GetValue() As Object Function ToHexString() As String Shared enumSeperatorCharArray As Char() Shared intType As Type Shared stringType As Type Shared fieldInfoHash As Hashtable Shared Const enumSeperator As String Shared Const maxHashElements As Integer Class HashEntry Inherits Object Public names As String() Public values As ULong() End Class End Enum
Daraus können Sie erkennen, dass die Elternklasse aller Enumerationen selbst ein Referenztyp ist (so merkwürdig das auch scheinen mag).
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.