Enums
Enums sind ein einfacher und komfortabler Weg um z. B. Status- oder Modus-Indikatoren zu speichern.
Jeder Entwickler braucht dieses Feature wohl tagtäglich, sei es über “built-in” Enumerationen wie System.IO.FileAccess oder über eigens definierte.
Basics
Hier als einfaches Beispiel ein Enum, das die Benutzungsart einer Software beschreibt:
public enum UsageMode { Private, Commercial, Education }
und deren Verwendung in einer Klasse “Software”:
public class Software { UsageMode usage = UsageMode.Commercial; public UsageMode Usage { get { return usage; } set { usage = value; } } }
Enums sind im Prinzip nichts anderes, als benannte Konstanten für Integer-Werte. Im Beispiel werden die Werte wie folgt implizit durchnummeriert.
public enum UsageMode { Private = 0, Commercial = 1, Education = 2 }
Dies lässt sich durch die schlichte Zuweisung eines beliebigen Integers aber ändern.
Dadurch, dass Enums neben benannten Konstanten eben auch numerische Werte darstellen, eignen sie sich auch sehr gut, um auf Arrays zu mappen.
public class Software { UsageMode usage = UsageMode.Commercial; string[] humanText = new string[] { "Private (non-commercial) usage", "Unlimited commercial usage", "Educational usage" }; public UsageMode Usage { get { return usage; } set { usage = value; } } public string HumanText { get { return humanText[(int)usage]; } } }
Hier wird also eine Enumeration benutzt, um einen strukturierten Zugriff auf das String-Array humanText zu ermöglichen.
Wenns mal etwas mehr sein soll
Was nun aber, wenn ein Int32 für die gewünschten Datentypen nicht mehr ausreicht? Kein Problem, der zugrundeliegende Typ einer Enum kann über Vererbung geändert werden. So lässt sich eine Enum problemlos mit einem Int64 (long) verwenden:
public enum UsageMode : long { Private = 2147483640L, Commercial = 2147483641L, Education = 2147483642L }
Es liegt auf der Hand, dass dies nur in sehr speziellen Fällen notwendig ist – kann aber trotzdem sehr nützlich sein, wenn es zum Beispiel darum geht, Zahlen > 2^31 über benannnte Konstanten zugänglich zu machen.
Bit-Flags
Bis jetzt sind wir immer davon ausgegangen, dass sich die einzelnen Enumerationswerte ausschliessen. Nicht selten jedoch, sollen sich die Werte auch kombinieren lassen. So ist es durchaus vorstellbar, dass eine Software zu privaten wie auch kommerziellen Zwecken eingesetzt wird.
Dies lässt sich erreichen, indem man für die einzelne Werte auschliesslich Potenzen von 2 verwendet:
public enum UsageMode { Private = 1, // 2^0 Commercial = 2, // 2^1 Education = 4 // 2^2 }
Nun, wieso denn das? Wenn man sich dann die Verwendung anschaut, wird auch das klar:
usage = UsageMode.Commercial | UsageMode.Private;
Die beiden Werte werden also mit einem | (OR) verbunden. Wenn man dies nun auf die Zahlenwerte überträgt
1 OR 2 = 3
sieht man, dass die Kombination einen Zahlenwert ergibt, die in der Enum nicht vorkommt und eindeutig diesem Paar zugewiesen werden kann.
Und das ist natürlich notwendig, da man ja den Wert der Enum auch abfragen will:
bool isPrivateUsage = (UsageMode.Private & usage) == UsageMode.Private;
Man ANDet also den aktuellen Wert (usage) mit dem zu prüfenden Wert (UsageMode.Private). Oder in Zahlen ausgedrückt:
1 AND 3 = 1
Nun liegt es natürlich nahe, dass man für die 3 hier auch einen Enum-Wert einführen könnte, zum Beispiel “PrivateAndCommercial”, dies würde die Benützung vereinfachen, da zur Abfrage bzw. Aktivierung nicht jedes mal mit Bit-Operatoren hantiert werden müsste. Solche Kombinationen sollte man einführen, sofern sie gebräuchlich sind.
Oftmals wird auch ein Wert benötigt, der indiziert, dass keiner der Werte aktiviert wurde. Dieser (und nur dieser!) sollte dann jeweils mit der Zahl 0 versehen werden.
[System.Flags()] public enum UsageMode { None = 0, Private = 1, Commercial = 2, PrivateAndCommercial = 3, Education = 4 }
System.Flags-Attribut
Hier wurde nun noch das System.Flags-Attribut eingeführt. Dieses ist zwar nicht unbedingt notwendig für dieses Verhalten, ist jedoch empfehlenswert. Einfluss hat das Attribut zum Beispiel auf die Ausgabe des Enum-Wertes über die ToString()-Methode:
usage = UsageMode.Private | UsageMode.Commercial; Console.WriteLine(usage.ToString()); // Ausgabe mit Flags: PrivateAndCommercial; Ausgabe ohne Flags: PrivateAndCommercial usage = UsageMode.Private | UsageMode.Education; Console.WriteLine(usage.ToString()); // Ausgabe mit Flags: Private, Commercial; Ausgabe ohne Flags: 3
Die kombinierten Werte werden also bei gesetztem Flags-Attribut mit Kommata getrennt, statt einfach als Zahl, ausgegeben. Dies kann sehr nützlich sein. So kann man zum Beispiel über die Split-Funktion von String so die einzelnen Werte sehr einfach in ein Array überführen.
Fazit
Enums bieten also ein sehr einfaches und flexibles Werkzeug, um mit Konstanten Werten zu hantieren. Gerade Anfänger lassen sich zu gerne von der Flags-Funktion abschrecken, da sie so sehr nach “Bits rumschieben” schmeckt. Die Beispiele zeigen jedoch, dass die Anwendung denkbar einfach und dafür umso Leistungsfähiger ist.
Hier noch zwei interessante Links in diesem Zusammenhang:
Enum Design-Guidelines von Krzysztof Cwalina
MSDN (C#)