Verwenden von Flags
von Herfried K. Wagner
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.
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
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
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 |
... | ... | ... | ... |
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
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)
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