Die Community zu .NET und Classic VB.
Menü

Verwenden von Flags

 von 

Einleitung 

Dieser Artikel beschreibt eine Programmiertechnik, die in der API-Programmierung unter Windows häufig zum Einsatz kommt. Durch die Benutzung der Technik kann einerseits die Lesbarkeit des Quellcodes erhöht, andererseits aber auch der Speicherbedarf der kompilierten Anwendung reduziert werden.

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

Definition von Flags  

Flags können in den meisten Programmiersprachen eingesetzt werden, um Informationen über ausgewählte Eigenschaften oder Optionen, die nur die boolesche Werte „Wahr“ und „Falsch“ annehmen können, in einem Wert zusammenzufassen, um sie bspw. an eine Prozedur zu übergeben. Dazu wird nicht für jede Option ein eigener Parameter vom Datentyp Boolean vorgesehen, wie man es intuitiv tun würde, sondern nur ein Parameter, meist vom Datentyp Long.

Betrachtet man das Prinzip genauer, sieht man den Grund für die Bezeichnung „Flag“ (zu Deutsch Flagge). Wie bei einem Schiff können gewisse Flaggen gesetzt sein oder nicht. Der Betrachter sieht nach, welche Flaggen gesetzt sind, also etwa ob es Krankheiten auf dem Schiff gibt.

Bewertung der Verwendung von Flags  

Die Liste zeigt einige Vorteile der im Folgenden an einem Beispiel genauer erläuterten Technik:

  • Beim Windows-API sind häufig Funktionen zu finden, die nur eine geringe Anzahl an Übergabeparametern haben, aber dem Programmierer doch zulassen, grössere Mengen an Informationen zu übergegeben. So ist es zum Beispiel möglich, für einen Parameter vom Typ Long mehrere Optionskonstanten anzugeben, die dann von der Funktion wieder in ihre Einzelteile zerlegt werden und entsprechende Aktionen auslösen bzw. Optionen einstellen.
  • Es ist nicht sinnvoll, für eine Prozedur mit zwei Boolean-Parametern diese Methode zu verwenden, allerdings wird dadurch das Programm auf eine höhere Abstraktionsebene gebracht. Es sollte der Programmierer selbst entscheiden, wann der Einsatz dieser Technik sinnvoll ist.
  • Es sollten Konstanten im Deklarationsabschnitt des Moduls angelegt werden, um die Verständlichkeit der Prozeduraufrufe zu erhöhen, d.h. der Programmierer sollte nicht alle möglichen Konstanten auswendig können müssen, sondern nur im Deklarationsteil die entsprechenden Konstanten suchen. Besonders bietet sich hierzu die Verwendung sogenannter Enumerationen bzw. Aufzählungstypen an, über die mehrere zusammengehörige Konstanten in einen logischen Zusammenhang gebracht werden können.
  • Häufig werden dadurch Prozeduraufrufe stark verkürzt, weshalb der Code leichter verständlich und besser wartbar wird.
Natürlich hat diese Technik auch Nachteile wie die durch die Bitbreite des Aufzählungsdatentyps beschränkte Anzahl an Optionen, die zusammengefasst werden können. Allerdings stellt dies in der Praxis selten ein Problem dar, da man meist mit wenigen Optionen auskommt und diese Technik nicht so weit treiben soll, logisch nicht zusammenhängende Dinge zusammenzufassen. Dies würde ja nur die Verständlichkeit des Quellcodes reduzieren

Vergleich anhand eines Beispiels  

Folgende Prozedur wäre ungeschickt, da aus dem Prozeduraufruf nicht ersichtlich ist, welche Parameter gewählt wurden, falls die genaue Bedeutung der Parameter der Prozedur nicht bekannt ist. Besonders bei langen Programmen, bei denen der Programmierer nicht die Möglichkeit hat, sofort die Deklarationen der Prozeduren anzusehen (etwa bei der Benutzung von Komponenten, die nicht im Quellcode vorliegen), wird es einem schwer fallen, schnell ein Verständnis für die Funktionsweise des Quellcodes zu erwerben:

Call FormatText(..., True, True, False, False, True)
.
.
.
Private Function FormatText( _
    ByVal Text As String, _
    ByVal UpperCase As Boolean, _
    ByVal TrimSpaces As Boolean, _
    ByVal Reverse As Boolean, _
    ByVal AddPeriod As Boolean, _
    ByVal RemoveTabs As Boolean _
) As String

Listing 1: Aufruf und Kopf einer Prozedur unter Verwendung mehrerer boolescher Parameter zur Optionsauswahl.

Die nachstehende Lösung desselben Problems ist bedeutend einfacher zu verstehen, da man bereits aus dem Aufruf der Prozedur leicht sehen kann, welche Optionen gewählt wurden. Im Folgenden ist wiederum ein Prozeduraufruf angegeben. Hier erkennt man sofort, dass die Prozedur irgendetwas mit der Formatierung von Text zu tun hat. Ausserdem ist nur mehr ein Parameter erforderlich, bei dem die einzelnen Konstanten bitweise durch ein logisches Oder verknüpft werden. Die benötigten Konstanten können in einem Aufzählungstyp zusammengefasst werden. Der Basistyp von Aufzählungen ist der Datentyp Long:

Private Enum FormatTextFlags
    UpperCase = 1&
    TrimSpaces = 2&
    Reverse = 4&
    AddPeriod = 8&
    RemoveTabs = 16&
End Enum
.
.
.
Call FormatText(..., UpperCase Or TrimSpaces Or RemoveTabs)
.
.
.
Private Function FormatText( _
    ByVal Text As String, _
    ByVal Format As FormatTextFlags _
) As String

Listing 2: Aufruf und Kopf einer Prozedur unter Verwendung eines Aufzählungstyps zur Darstellung gewählter Optionen.

Nach welchem Schema die Werte der Konstanten festgelegt werden müssen und wie die Auswertung der gewählten Optionen innerhalb der Implementierung der Prozedur erfolgt, wird im Beispiel im nächsten Abschnitt erklärt.

Ermittlung ausgewählter Optionen  

In folgendem Beispiel wird eine Funktion FormatText implementiert, die den in Text übergebenen Text entsprechend den in FormatTextFlags übergebenen Formatierungseigenschaften formatiert. Die Konstanten des Aufzählungstyps FormatTextFlags enthalten die Werte für die angebotenen Optionen.

Entscheidend bei der Wahl der Werte der Optionskonstanten ist, dass sich der Wert von Konstante zu Konstante immer verdoppelt. Nur so kann innerhalb der Prozedur, an welche die zusammengefassten Werte übergeben werden, erkennen, welche Optionen ausgewählt wurden. Die Verdopplung des Wertes entspricht der Bildung von Zweierpotenzen, bei deren Darstellung im Rechner dann immer genau ein Bit gesetzt wird. In einem vereinfachten Modell (hier verwenden wir nur acht Bit und unterscheiden nicht zwischen negativen und positiven Zahlen) kann man sich das wie folgt vorstellen:

Darstellung von Zahlen im Computer

Basis Exponent Dezimale Entsprechung Darstellung im Computer
2 0 1 0000 0001
2 1 2 0000 0010
2 2 4 0000 0100
2 3 8 0000 1000
... ... ... ...

Abbildung 1: Funktionsweise von Flags

Wie man leicht erkennen kann, ist es mit den 32 Bits des Datentyps Long möglich, bis zu 32 Wahrheitswerte zu repräsentieren. Durch das „Verodern“ der Konstanten werden dann die entsprechenden Bits gesetzt, also 00000010 Oder-verknüpft mit 0010000 ergibt dann 00100010.

Um in der Prozedur, hier FormatText, zu prüfen, ob eine bestimmte Option gewählt wurde, wird geprüft, ob das entsprechende Bit der Optionskonstante in dem in Format übergebenen Wert enthalten ist. Dazu wird folgender Code verwendet:

If CBool(FormatTextFlags And UpperCase) Then
    
    ' 'UpperCase' ist gewählt.
End If

Listing 3: Ermitteln, ob eine Option gesetzt wurde.

Dabei erfolgt eine bitweise Und-Verknüpfung. Ist das Ergebnis gleich null, dann ist die Option nicht gewählt, andernfalls schon. Man könnte anstelle des Vergleichs mit der Optionskonstante zur Überprüfung auf Auswahl der Option auch einen Vergleich auf Ungleichheit zu null durchführen. Auf diese Weise kann jede Option abgefragt und eine entsprechende Aktion ausgeführt werden. Zum Aufruf von FormatText mit mehreren Optionen werden im Parameter Format alle gewünschten Optionen durch ein bitweises Oder verknüpft:

... = FormatText(..., TrimWhitespace Or RemoveTabs)

Listing 4: Kombinieren von Optionen.

Schlusswort  

Wie wir gesehen haben, können durch Verwendung von Flags Komplexität und Umfang von Prozeduren erheblich reduziert werden. Flags sind dann geeignet, wenn sich die Schnittstelle der Prozedur bei Hinzufügen neuer Optionsparameter nicht ändern soll. Würde man für jede Option einen eigenen Parameter vom Datentyp Boolean vorsehen, hätte eine neue Option die Folge, dass Code, der auf der alten Version aufbaut, umgeschrieben werden müsste. Flags erleichtern also in vielerlei Hinsicht die Programmierung und sollten daher im Standardrepertoire jedes fortgeschrittenen Programmierers ihren Platz gefunden haben.

Vollständiges Codebeispiel  

Private Enum FormatTextFlags
    UpperCase = 1&
    TrimSpaces = 2&
    Reverse = 4&
    AddPeriod = 8&
    RemoveTabs = 16&
End Enum

Private Sub Main()
    Const Text As String = "He said:    H ello" & vbTab & "World!   "
    Const NL As String = vbNewLine
    Call MsgBox( _
        "Originalzeichenfolge: """ & Text & """" & NL & NL & _
        "Formatoptionen: 'UpperCase'" & NL & _
        "Ergebnis: """ & FormatText(Text, UpperCase) & """" & NL & _
        NL & _
        "Formatoptionen: 'TrimSpaces', 'RemoveTabs'" & NL & _
        "Ergebnis: """ & _
        FormatText(Text, TrimSpaces Or RemoveTabs) & """" & NL & _
        NL & _
        "Formatoptionen: 'Reverse', 'AddPeriod', 'UpperCase'" & NL & _
        "Ergebnis: """ & _
        FormatText( _
            Text, Reverse Or AddPeriod Or UpperCase _
        ) & """" & NL & _
        NL & _
        "Formatoptionen: 'TrimSpaces', 'AddPeriod', 'RemoveTabs', " & _
        "'UpperCase'" & NL & _
        "Ergebnis: """ & _
        FormatText( _
            Text, _
            TrimSpaces Or AddPeriod Or RemoveTabs Or UpperCase _
        ) & """" _
    )
End Sub

Private Function FormatText( _
    ByVal Text As String, _
    ByVal Format As FormatTextFlags _
) As String
    If CBool(Format And UpperCase) Then
        Text = UCase$(Text)
    End If
    If CBool(Format And TrimSpaces) Then
        Text = Replace(Text, " ", "")
    End If
    If CBool(Format And Reverse) Then
        Text = StrReverse(Text)
    End If
    If CBool(Format And AddPeriod) Then
        Text = Text & "."
    End If
    If CBool(Format And RemoveTabs) Then
        Text = Replace(Text, vbTab, "")
    End If
    FormatText = Text
End Function

Listing 5: Das vollständige Beispiel.