Die Community zu .NET und Classic VB.
Menü

Sind Aufzählungstypen noch zeitgemäss?

 von 

Einleitung 

Viele populäre Programmiersprachen unterstützen Aufzählungstypen zur Gruppierung mehrerer Konstanten eines bestimmten ganzzahligen Datentyps. Die Vorteile gegenüber herkömmlichen Lösungen wie nur namentlich als zusammengehörig erkennbaren Konstanten sowie die Abstraktion von den eigentlichen Konstantenwerten durch Vergabe bedeutungsvoller Namen sind offensichtlich. In Anbetracht der sonstigen Entwicklung von Programmiersprachen wie dem zunehmenden Einzug von Objektorientierung muss erneut untersucht werden, ob Aufzählungstypen in der präsenten Form eine ideale Lösung darstellen und ob nicht alternative Möglichkeiten existieren, welche über das Potential verfügen, allfällige Beschränkungen von Aufzählungstypen aufzuheben.

Hinweis:
Die Erstveröffentlichung dieses Tutorials finden Sie unter http://dotnet.mvps.org/dotnet/articles/enums/

Aufzählungstypen und Flags  

Im Unterschied zur Programmiersprache Java, die bis zur Version 5.0 (2004) keine Aufzählungstypen unterstützte, haben diese in Visual Basic seit Version 5.0 (1997) Tradition. Bei Aufzählungstypen handelt es sich um Typen, die eine feste Anzahl benannter Konstanten gruppieren. Neben der Gruppierung von der Bedeutung nach zusammengehöriger Konstanten war sicher auch die dadurch verbesserte Unterstützung der Codierung über IntelliSense in Form einer Auswahlliste möglicher Konstanten für die Einführung von Aufzählungstypen in Visual Basic ausschlaggebend. Dies ist bei ungruppierten Konstanten nicht möglich, da der Bezug zwischen formalem Parametertyp und den möglichen Konstanten nicht durch die Entwicklungsumgebung hergestellt werden kann. In Umgebungen, die Aufzählungstypen nicht unterstützen, werden häufig Präfixe den Namen der Konstanten vorangestellt, um deren Zusammengehörigkeit Ausdruck verleihen, etwa WS_BORDER, WS_CAPTION, WS_CHILD etc. in der Win32-Programmierschnittstelle.

In verschiedenen Programmiersprachen unterscheiden sich Aufzählungstypen in der gebotenen Typsicherheit. So fassen manche Programmiersprachen Konstanten einer Aufzählung lediglich als Pseudonyme bestimmter numerischer Werte auf, sodass es möglich ist, Werte unterschiedlicher Aufzählungen und numerischer Datentypen miteinander zu kombinieren oder einander zuzuweisen und im Quellcode zu mischen. In anderen Programmiersprachen wie etwa Visual Basic .NET mit aktiviertem Option Strict sind keine impliziten Typumwandlungen zwischen Werten verschiedener Aufzählungstypen und ganzzahliger Datentypen vorgesehen. Stattdessen sind zu diesem Zweck explizite Typumwandlungen im Quellcode erforderlich.

Aufzählungstypen besitzen in .NET einen ganzzahligen Basistyp, der den Typ der einzelnen Konstanten der Aufzählung vorgibt. Während die Anzahl an Konstanten, die in der Aufzählung definiert werden können, keiner praktisch relevanten Begrenzung unterliegt, wird die Anzahl der in Form vom Flags miteinander in einem Wert des Aufzählungstyps kombinierbaren Konstanten durch die Bitbreite des Basistyps beschränkt. Unter Flags versteht man benannte boolesche Variablen. Der kompakteren Handhabung und leichteren Persistierbarkeit bei zugleich geringen Kosten für Verarbeitung und Speicherung wegen können mehrere Flags in einem Wert eines Aufzählungstyps zusammengefasst gespeichert werden. Die gesetzten Bits geben Aufschluss über die gesetzten Flags. In einer Aufzählung des Datentyps Integer, einem 32-Bit-Ganzzahldatentyp, können folglich bis zu 32 benannte Flags definiert werden.

In der Bitrepräsentation jeder der Konstanten einer Aufzählung von Flags ist genau eines der 32 Bits gesetzt. Wenngleich die Begrenzung auf 32 Flags in den meisten Fällen ebenfalls nicht von praktischer Bedeutung ist, kann es doch vorkommen, dass eine Aufzählung im Laufe der Zeit durch Erweiterung auf über 32 Flags anwachsen müsste. Folgendes Listing zeigt die Deklaration eines Aufzählungstyps in Visual Basic .NET. Das Attribut Flags zeigt an, dass mehrere Konstanten der Aufzählung in einem Wert kombiniert gespeichert werden. Die Werte der einzelnen Konstanten entsprechen Zweierpotenzen.

<Flags()> _
Public Enum Letter
  A = 1       ' = 2^0(10) = 0...00001(2) =  1(16).
  B = A << 1  ' = 2^1(10) = 0...00010(2) =  2(16).
  C = A << 2  ' = 2^2(10) = 0...00100(2) =  4(16).
  D = A << 3  ' = 2^3(10) = 0...01000(2) =  8(16).
  E = A << 4  ' = 2^4(10) = 0...10000(2) = 10(16).
End Enum

Listing 1: Deklaration eines Aufzählungstyps mit mehreren benannten Flags.

Neben der Anzeige von Auswahllisten in IntelliSense zur Erleichterung der Codierung können Programmiersprachen auch das Verzweigen anhand eines Aufzählungswertes erlauben. In Visual Basic .NET steht zu diesem Zweck die Select Case-Anweisung zur Verfügung. C# bietet mit switch ebenfalls eine Verzweigungsanweisung. Im Gegensatz zu Select Case muss es sich bei den Verzweigungsausdrücken von switch jedoch um konstante Ausdrücke handeln, weshalb das Verzweigen zwar bei mittels Enum definierten Aufzählungstypen funktioniert, nicht aber mit Aufzählungen wie Color, Pens und Brushes, deren Elemente keine konstanten Werte besitzen.

Beschränkungen von Aufzählungstypen  

Bei der Untersuchung der Grenzen von Aufzählungstypen ist es notwendig, zwischen jenen zu unterscheiden, deren Konstanten nicht bitweise verknüpft werden können und denjenigen, bei denen dies vorgesehen ist, um mehrere Optionen in einem Wert des Aufzählungstyps zu speichern. Zusammenfassend können die folgenden drei Kritikpunkte an Aufzählungstypen in der Form, wie sie in Visual Basic .NET und C# verfügbar sind, festgestellt werden:

  • Der Basistyp von Aufzählungstypen (Schlüsselwort Enum) muss ein ganzzahliger numerischer Datentyp sein. Es besteht keine Möglichkeit, Instanzen von Verweistypen als Werte von Elementen eines Aufzählungstyps zu spezifizieren, um zusätzliche Daten an die Elemente zu binden.
  • Die Bitrepräsentation der einzelnen Optionen sollte ein für den Entwickler in den meisten Fällen ein transparentes Implementierungsdetail darstellen. Setzen und Auswerten gesetzter Flags in einem Wert eines Aufzählungstyps erfordern jedoch Wissen über Bitoperationen und die interne Implementierung von Aufzählungstypen.
  • Die dabei zum Einsatz kommenden bitweisen Operatoren sind semantisch unpassend. So wird der Operator Or benutzt, um zwei Flags Letter.A und Letter.B zu kombinieren, wobei eigentlich ein And („Optionen A und B sind gesetzt“) von der Bedeutung her treffender wäre. Auch das Auswerten gesetzter Flags mittels der Operatoren And und CBool (AIsSelected = CBool(SelectedLetters And Letter.A)) ist unintuitiv und irreführend.
  • Wird ein Aufzählungstyp zur Benennung mehrerer kombinierbarer Flags verwendet, so ist die Anzahl an möglichen Optionen durch die Bitbreite des Basistyps der Aufzählung beschränkt. Dies kann zu Problemen bei der Erweiterung des Aufzählungstyps um zusätzliche Elemente führen.

Aufzählungstypen für Elemente eines beliebigen Datentyps  

In einigen Szenarien ist es sinnvoll, eine Aufzählung von benannten Instanzen eines Datentyps bereitzustellen, die in IntelliSense-Auswahllisten bei Benutzung des Datentyps im Quellcode angezeigt wird. Dies wird in .NET über gemeinsame Eigenschaften oder Felder einer Klasse bewerkstelligt. Der Klasse, die normalerweise nicht instanzierbar ist, kommt dabei lediglich die Funktion des Gruppierens der benannten Instanzen zu. In der Klassenbibliothek von .NET ist dies etwa bei den Datentypen Color, Pen (Pens), Brush (Brushes) und einigen anderen der Fall, wobei im Falle des Datentyps Color die eigenen gemeinsamen Eigenschaften in der Auswahlliste angezeigt werden.

Aufzählungstypen mit Elementen eines beliebigen Datentyps können in .NET über als NotInheritable markierte Klassen, deren Instanzierung durch Definition eines privaten Konstruktors unterbunden wird, implementiert werden. Die Elemente der Aufzählung können entweder als gemeinsame Eigenschaften oder Felder definiert werden. Im nachstehenden Listing wird der Quellcode eines Aufzählungstyps mit Elementen eines bestimmten Datentyps wiedergegeben.

Imports System.ComponentModel
Imports System.Globalization
Imports System.Reflection

''' <completionlist cref="WindowParts"/>
<TypeConverter(GetType(WindowPartsConverter))> _
Public Class WindowPart
    Private m_Name As String

    Public Sub New(ByVal Name As String)
        Me.Name = Name
    End Sub

    Public Property Name() As String
        Get
            Return m_Name
        End Get
        Set(ByVal Value As String)
            m_Name = Value
        End Set
    End Property

    Public Overrides Function ToString() As String
        Return m_Name
    End Function
End Class

Public NotInheritable Class WindowParts
    Private Sub New()
        '
    End Sub

    Public Shared ReadOnly Property Border() As WindowPart
        Get
            Return New WindowPart("Border")
        End Get
    End Property

    Public Shared ReadOnly Property TitleBar() As WindowPart
        Get
            Return New WindowPart("TitleBar")
        End Get
    End Property

    Public Shared ReadOnly Property ClientArea() As WindowPart
        Get
            Return New WindowPart("ClientArea")
        End Get
    End Property
End Class

Listing 2: Implementierung einer Auflistung von Elementen des Typs WindowPart

Mittels des Attributs cref des undokumentierten completionlist-Elements kann im XML-Kommentar eines Datentyps der Datentyp festgelegt werden, dessen gemeinsame Mitglieder in der IntelliSense-Auswahlliste angezeigt werden sollen. Die Entwicklungsumgebung Visual Studio 2005 bietet eine Auswahl an möglichen Werten über IntelliSense an, wenn einer Variablen oder Eigenschaft eines bestimmten Datentyps ein Wert zugewiesen wird oder ein Wert in einem formalen Parameter dieses Datentyps übergeben werden soll. Leider funktioniert die automatische Anzeige der Auswahlliste nicht bei als ParamArray markierten Parametern des Datentyps. Folgende Abbildung zeigt die von Visual Studio 2005 angebotene Auswahl der gemeinsamen Mitglieder der Klasse WindowParts bei einer Zuweisung an eine Variable des Datentyps WindowPart.


Abbildung 1: Bildschirmfoto des Texteditors von Visual Basic 2005 mit IntelliSense-Auswahlliste für den Datentyp WindowParts.

Das completionlist-Element wird in Visual Studio 2005 anscheinend nur in Visual Basic .NET herangezogen, um entsprechende Auswahllisten anzubieten. In den Programmiersprachen C# und C++/CLI kann zwar das XML-Element angegeben werden, die Entwicklungsumgebung macht jedoch keinen Gebrauch davon. Wenngleich die Verwendung undokumentierter Elemente immer kritisch betrachtet werden muss, dürfte der Einsatz von completionlist unproblematisch sein. Insbesondere beim Bereitstellen wiederverwendbarer Bibliotheken bietet es sich an, completionlist zu spezifizieren, um ein zu den Klassen der Klassenbibliothek von .NET konsistentes Verhalten zu erzielen.

Die von completionlist gebotene Möglichkeit, bei einem Datentyp jenen Datentyp anzugeben, dessen gemeinsame Mitglieder in IntelliSense angezeigt werden, erweist sich zwar als praktisch, ist jedoch unzureichend. Im Falle des Datentyps Brush werden in der Auswahlliste nur jene Pinsel angezeigt, die von der Klasse Brushes bereitgestellt werden, obwohl auch andere Klassen wie etwa SystemBrushes eine Auswahl benannter Pinsel anbieten. Bei der Kompilierung des Datentyps Brush, im Zuge derer auch die XML-Dokumentationsdatei erstellt wird, kann der Compiler nicht feststellen, welche Typen für die Auswahlliste relevante Eigenschaften enthalten. So könnte in einem Projekt eine eigene Auflistung spezieller Pinsel definiert werden, die häufig zum Einsatz gelangen.

Aus Sicht der Erweiterbarkeit und Flexibilität wäre es daher wünschenswert, die Typzuordnung in umgekehrte Richtung vornehmen zu können. Anstatt des completionlist-Elements, das einem Datentyp maximal einen Datentyp zum Befüllen der Auswahlliste zuordnen kann, könnte ein direkt an den die Aufzählung enthaltenden Datentypen anzugebendes XML-Element treten. Allerdings ist die Implementierung eines Datentyps zur Aufzählung von Elementen eines Datentyps oder dessen Untertypen recht aufwendig, zumal Klassen, selbst in Form der in C# verfügbaren statischen Klassen, von der Semantik her kein geeignetes Mittel zur Gruppierung zusammengehöriger benannter Instanzen darstellen. Problematisch ist jedoch auch das Einführen eines neuen syntaktischen Konstrukts zur semantisch korrekteren Auszeichnung derartiger Auszählungstypen für Elemente eines beliebigen Datentyps, da einige Klassen, darunter Color, bereits selbst eine Auswahl benannter Elemente bereitstellen.

Des Weiteren besteht eine Doppelgleisigkeit zwischen IntelliSense-Auswahlliste und der Auswahl an möglichen Eigenschaftswerten im Eigenschaftenfenster von Visual Studio. Während der Inhalt ersterer, wie zuvor dargelegt, über das completionlist-Element festgelegt wird, bezieht letztere ihren Inhalt über eine von der Klasse TypeConverter abgeleiteten Klasse. Hierbei besteht zwar nicht mehr die Einschränkung auf einen einzigen Aufzählungstyp, jedoch ist der Implementierungsaufwand immens, falls der Typumwandler Elemente mehrerer Aufzählungstypen anbieten können soll. Der Typumwandler kann über das Attribut TypeConverter einem oder mehreren Datentypen zugeordnet werden. Im folgenden Listing ist die Implementierung eines Typumwandlers für den zuvor definierten Aufzählungstyp zu sehen.

Public Class WindowPartsConverter
    Inherits TypeConverter

    Public Overrides Function CanConvertFrom( _
        ByVal context As ITypeDescriptorContext, _
        ByVal sourceType As Type _
    ) As Boolean
        Return (sourceType Is GetType(String))
    End Function

    Public Overrides Function ConvertFrom( _
        ByVal context As ITypeDescriptorContext, _
        ByVal culture As CultureInfo, _
        ByVal value As Object _
    ) As Object
        If TypeOf value Is String Then
            Return New WindowPart(DirectCast(value, String))
        Else
            Throw _
                New ArgumentException( _
                    "'value' must be of type 'String'." _
                )
        End If
    End Function

    Public Overrides Function CanConvertTo( _
        ByVal context As ITypeDescriptorContext, _
        ByVal destinationType As Type _
    ) As Boolean
        Return (destinationType Is GetType(WindowPart))
    End Function

    Public Overrides Function ConvertTo( _
        ByVal context As ITypeDescriptorContext, _
        ByVal culture As CultureInfo, _
        ByVal value As Object, _
        ByVal destinationType As Type _
    ) As Object
        Return DirectCast(value, WindowPart).Name
    End Function

    Public Overrides Function GetStandardValuesSupported( _
        ByVal context As ITypeDescriptorContext _
    ) As Boolean
        Return True
    End Function

    Public Overrides Function GetStandardValues( _
        ByVal context As ITypeDescriptorContext _
    ) As StandardValuesCollection

        ' Bestimmen der gemeinsamen Eigenschaften des Typs 'WindowPart'.
        Dim StandardValues As New List(Of WindowPart)()
        Dim SharedProperties() As PropertyInfo = _
            GetType(WindowParts).GetProperties( _
                BindingFlags.Public Or _
                BindingFlags.Static _
            )
        For Each Prop As PropertyInfo In SharedProperties
            StandardValues.Add( _
                DirectCast( _
                    Prop.GetValue(GetType(WindowParts), Nothing), _
                    WindowPart _
                ) _
            )
        Next Prop
        Return _
            New StandardValuesCollection( _
                StandardValues _
            )
    End Function
End Class

Listing 3: Implementierung des Typumwandlers zur wechselseitigen Typumwandlung zwischen den Datentypen WindowPart und String.

Die oben angegebene Klasse WindowPartConverter stellt nur die gemeinsamen Eigenschaften des Datentyps WindowPart bereit. Die Implementierung der Funktion GetStandardValues kann auch dahingehend erweitert werden, die gemeinsamen Mitglieder anderer Datentypen zurückzugeben. Folgendes Listing zeigt die Implementierung einer weiteren Aufzählungsklasse ListViewWindowParts und einer um Unterstützung für Elemente dieser Klasse erweiterte Implementierung von GetStandardValues. Besitzt ein Objekt nun eine Eigenschaft des Typs WindowPart, werden bei dessen Anzeige in einem PropertyGrid-Steuerelement, etwa im Eigenschaftenfenster von Visual Studio, sowohl die im Datentyp WindowParts als auch in ListViewWindowParts definierten Elemente zur Auswahl angeboten.

Public Class WindowPartsConverter
    Inherits TypeConverter

    ...

    Public Overrides Function GetStandardValues( _
        ByVal context As ITypeDescriptorContext _
    ) As StandardValuesCollection
        Dim StandardValues As New List(Of WindowPart)
        Dim SharedProperties() As PropertyInfo = _
            GetType(WindowParts).GetProperties( _
                BindingFlags.Public Or _
                BindingFlags.Static _
            )
        For Each Prop As PropertyInfo In SharedProperties
            StandardValues.Add( _
                DirectCast( _
                    Prop.GetValue(GetType(WindowParts), Nothing), _
                    WindowPart _
                ) _
            )
        Next Prop
        StandardValues.Add(ListViewWindowParts.ColumnHeader)
        StandardValues.Add(ListViewWindowParts.Item)
        Return _
            New StandardValuesCollection( _
                StandardValues _
            )
    End Function

    ...
End Class

Public NotInheritable Class ListViewWindowParts
    Private Sub New()
        '
    End Sub

    Public Shared ReadOnly Property ColumnHeader() As WindowPart
        Get
            Return New WindowPart("ColumnHeader")
        End Get
    End Property

    Public Shared ReadOnly Property Item() As WindowPart
        Get
            Return New WindowPart("Item")
        End Get
    End Property
End Class

Listing 4: Erweiterung der Klasse WindowPartConverter, sodass auch Eigenschaften des Datentyps ListViewWindowParts unterstützt werden.

Aufzählungen von Flags eines beliebigen Datentyps  

Zur Modellierung von Aufzählungen mehrerer Flags eines beliebigen Datentyps bietet sich die Verwendung von Mengen an. An Stelle des Setzens oder Prüfens einzelner Bits in einem Wert treten Mengenoperationen. Damit werden die Einschränkungen der Anzahl an möglichen Flags durch die Bitbreite des Basistyps des Aufzählungstyps und die Beschränkung auf ganzzahlige Werte umgangen. Auf eine Implementierung eines geeigneten Mengentyps wird hier verzichtet. Im Einfachsten Fall kann hierzu eine generische Liste (Klasse List(Of T)) herangezogen werden. Ideal wäre eine direkte Unterstützung typsicherer Mengentypen durch das .NET Framework und die .NET-Programmiersprachen in Form entsprechender Klassen und Operatoren, die einen intuitiven Umgang erlauben.

Betrachten wir das Beispiel einer Personenverwaltung, bei der Personen (Klasse Person) neben einem Namen (Eigenschaft Name) bestimmte Rollen (Eigenschaft Roles) einnehmen können. Die Menge möglicher Rollen soll dabei beliebig erweiterbar sein, einerseits durch Instanzierung von Rollen, andererseits durch Ableiten vom Rollenbasistyp. Eine Person kann keine, eine oder mehrere Rollen besitzen. Welche Rollen eine Person bekleidet, kann sich während der Lebenszeit eines Person-Objekts ändern.

Zur Darstellung der Rollen einer Person sind mehrere Ansätze denkbar. Kann eine Person nur genau eine Rolle einnehmen, können verschiedene Rollen durch Ableiten von der Klasse Person realisiert werden. Das Ändern der von einer Person bekleideten Rolle kann bei dieser Lösung nachträglich nur mit grossem Aufwand geändert werden. Anstatt verschiedene Rollen durch Untertypen zu realisieren könnte man die Klasse Person um eine Eigenschaft Roles erweitern, deren Typ ein herkömmlicher Enum-Aufzählungstyp ist. Mehrere Rollen können binär zusammengefasst werden. Nachteilig an dieser Lösung ist, dass die Menge der verfügbaren Rollen schwer erweitert werden kann. Ausserdem besteht keine Möglichkeit, die Rollen mit Eigenschaften und ggf. Methoden auszustatten.

Eine dritte Möglichkeit bietet eine an das Rollen-Entwurfsmuster angelehnte Implementierung, bei der Rollen als Instanzen einer Klasse Role oder ihrer Untertypen repräsentiert werden. Der Typ der Eigenschaft Roles ist dann im einfachsten Fall eine Liste des Typs List(Of Role) oder idealerweise eine dynamisch veränderbare Menge [Set](Of Role). Bei dieser Lösung bestehen die Probleme der fehlenden oder schwierigen Erweiterbarkeit von Rollenzahl und -typ und der Änderung der von einer Person bekleideten Rollen zur Laufzeit nicht mehr.

Der einfacheren Benutzbarkeit sollen in der Basisklassenbibliothek einige allgemeine Rollen definiert werden. Dies geschieht durch Ableiten der Klassen Passenger und Agent von der Klasse Role. Diese Standardrollen werden im Aufzählungstyp Roles ähnlich dem zuvor gezeigten Aufzählungstyp WindowParts implementiert. Benutzer der Bibliothek können durch Ableiten von Role weitere Rollen definieren. Auch das Gruppieren dieser benutzerdefinierten Rollen in eigenen Aufzählungen ist denkbar. Allerdings kann über das completionlist-Element zur Zeit nur ein Aufzählungstyp mit einem Datentyp verknüpft werden. Die Eigenschaften der Rollenaufzählungen können, je nach Erfordernis, Verweise auf bestehende Rolleninstanzen oder auf neu angelegte Instanzen zurückgeben.

''' <completionlist cref="Roles"/>
Public MustInherit Class Role
    '
End Class

Public Class Passenger
    Inherits Role
End Class

Public Class Agent
    Inherits Role
End Class

Public NotInheritable Class Roles
    Private Shared m_Passenger As New Passenger()
    Private Shared m_Agent As New Agent()

    Private Sub New()
        '
    End Sub

    Public Shared ReadOnly Property Passenger() As Role
        Get
            Return m_Passenger
        End Get
    End Property

    Public Shared ReadOnly Property Agent() As Role
        Get
            Return m_Agent
        End Get
    End Property
End Class

Public Class Person
    Private m_Name As String
    Private m_Roles As New List(Of Role)()

    Public Property Name() As String
        Get
            Return m_Name
        End Get
        Set(ByVal Value As String)
            m_Name = Value
        End Set
    End Property

    Public ReadOnly Property Roles() As List(Of Role)
        Get
            Return m_Roles
        End Get
    End Property
End Class

Listing 5: Implementierung einer Personenklasse mit Unterstützung für das Zuweisen mehrerer Rollen.

Aufgrund des Ersetzbarkeitsprinzips kann einer Eigenschaft des Datentyps Role ein Verweis auf eine Instanz des Datentyps Role bzw. eines davon abgeleiteten Datentyps zugewiesen werden. Allerdings ist hierbei die Anzahl an möglichen Elementen nicht fest, wohl aber jene an bei der Kompilierung bekannten gemeinsamen öffentlichen Mitglieder des Typs oder eines seiner Untertypen. In der IntelliSense-Auswahlliste bei Zuweisung einer Variablen des Datentyps Role sollten demnach die gemeinsamen Mitglieder aller bekannten Datentypen angeboten werden, deren Datentyp Role oder einer der davon abgeleiteten Datentypen ist. Besitzt die Eigenschaft, der ein Wert zugewiesen werden soll, einen spezifischeren Datentyp als Role, so wird eine entsprechende Auswahl der gemeinsamen Mitglieder angezeigt.

Der Wertebereich von Aufzählungen  

Bei der Implementierung von Aufzählungstypen mit erweiterter Funktionalität ist von Bedeutung, ob ein Aufzählungstyp eine fester Menge von Elementen besitzt oder über eine mittels verschiedener Mechanismen erweiterbare Elementmenge verfügt. Weiterhin ist zu unterscheiden, auf welchem Wege eine Erweiterung der Aufzählung möglich ist. Durch Ableiten vom Elementtyp können speziellere Elementtypen erstellt werden, die aufgrund des Ersetzbarkeitsprinzips anstelle der generischeren Elementtypen benutzt werden können. Andererseits können neue Aufzählungen dieser Typen erstellt werden, welche die Liste von zur Kompilierungszeit bekannter benannter Instanzen erweitert. IntelliSense soll anschliessend die Möglichkeit der Auswahl aus allen benannten Instanzen bieten oder ggf. auch das Anlegen eigener Instanzen zur Laufzeit unterstützen.

Folgendes Listing zeigt die Implementierung einer generischen Klasse ArithmeticOperation zur Darstellung eines arithmetischen Operators. Von dieser Klasse können konkrete Operatoren abgeleitet und die in der Basisklasse definierte Methode Eval entsprechend der repräsentierten Operation überschrieben werden. Die Klasse ArithmeticOperation selbst bietet einige vordefinierte Operatoren in Form einer mit gemeinsamen Eigenschaften implementierten Aufzählung an. Auch hier ist eine Erweiterung sowohl durch Vererbung als auch durch Bereitstellen zusätzlicher Klassen für arithmetische Operationen denkbar. Operatoren können anschliessend einer im allgemeinen Typ ArithmeticOperation typisierten Variablen oder Eigenschaft zugewiesen und polymorph genutzt werden, indem die Methode Eval aufgerufen wird.

''' <completionlist cref="ArithmeticOperation"/>
Public MustInherit Class ArithmeticOperation
    Public Sub New()
        '
    End Sub

    Public MustOverride Function Eval( _
        ByVal lhs As Double, _
        ByVal rhs As Double _
    ) As Double

    Private Shared m_Add As New Add()
    Private Shared m_Subtract As New Subtract()
    Private Shared m_Multiply As New Multiply()
    Private Shared m_Divide As New Divide()

    Public Shared ReadOnly Property Add() As ArithmeticOperation
        Get
            Return m_Add
        End Get
    End Property

    Public Shared ReadOnly Property Subtract() As ArithmeticOperation
        Get
            Return m_Subtract
        End Get
    End Property

    Public Shared ReadOnly Property Multiply() As ArithmeticOperation
        Get
            Return m_Multiply
        End Get
    End Property

    Public Shared ReadOnly Property Divide() As ArithmeticOperation
        Get
            Return m_Divide
        End Get
    End Property
End Class

Public Class Add
    Inherits ArithmeticOperation

    Public Overrides Function Eval( _
        ByVal lhs As Double, _
        ByVal rhs As Double _
    ) As Double
         Return lhs + rhs
    End Function
End Class

Public Class Subtract
    Inherits ArithmeticOperation

    Public Overrides Function Eval( _
        ByVal lhs As Double, _
        ByVal rhs As Double _
    ) As Double
         Return lhs - rhs
    End Function
End Class

Public Class Multiply
    Inherits ArithmeticOperation

    Public Overrides Function Eval( _
        ByVal lhs As Double, _
        ByVal rhs As Double _
    ) As Double
         Return lhs * rhs
    End Function
End Class

Public Class Divide
    Inherits ArithmeticOperation

    Public Overrides Function Eval( _
        ByVal lhs As Double, _
        ByVal rhs As Double _
    ) As Double
         Return lhs / rhs
    End Function
End Class

Listing 6: Implementierung arithmetischer Operatoren in Form von Klassen.

Schlusswort  

Mittels der von .NET und den .NET-Programmiersprachen angebotenen technischen und syntaktischen Möglichkeiten ist eine semantisch saubere Implementierung von Aufzählungen mit Elementen eines beliebigen Datentyps nicht möglich. Ist zudem noch die von Flags bekannte Mengensemantik gefragt, müssen zusätzliche Klassen implementiert oder Listen benutzt werden. Eine saubere architekturelle Trennung zwischen Attributen und XML-Kommentaren ist ebenfalls nicht vorhanden. Während die Anzeige von Klassen und deren Mitgliedern in Werkzeugkasten, Codeeditor und Eigenschaftenfenster der Entwicklungsumgebung über Attribute gesteuert wird, wird der Inhalt der IntelliSense-Auswahlliste über ein XML-Kommentarelement gesteuert. Das Nichtvorhandensein von dessen Dokumentation lässt vermuten, dass man sich der Unzulänglichkeit der momentanen Lösung bewusst ist.

Weiterführende Literatur  

Ihre Meinung  

Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.