Typsicheres Enum: Wenn ein Enum nicht ausreicht!

Kennst du das?

Du möchtest ein wenig sauberen Code schreiben und entscheidest dich dazu ein enum zu verwenden.

Doch im selben Augenblick stellt du fest, du musst eigentlich andere Werte zusammenfassen. Du brauchst mehr als nur eine simple Auflistung maskierter Werte.

Beispielsweise möchtest du fest definierte strings in einem enum zusammenfassen.

Passiert zwar nicht häufig, aber diese Mommente gibt es!

Die Vorteile eines Enums

Ein Enum ist ziemlich handlich. Schnell geschrieben und immer besser als irgendwelche magic values im eigenen Quellcode.

Es definiert in der Domain bestimmte Begriffe und verspricht bessere Lesbarkeit.

Vorrausgesetzt ein enum wird auch wie ein enum Behandelt.

Die Nachteile eines Enums

Bereits solch ein einfaches Konstrukt birgt schon etwas dummes:

public enum Shape
{
    None,
    Cuboid,
    VerticalCylinder,
    HorizontalCylinder
}

Ein enum ist 0 bassiert. Mit anderen Worten: Der erste Wert, der geschrieben wurde, ist immer eine 0. Oder anders gesagt, wenn man eine Instanz eines Enums erstellt, wird diese standardmäßig mit dem ersten Wert initialisiert.

Man wird also gezwungen ein 0-Wert anzugeben. Macht man das nicht, weiß man nicht, ob der Wert bereits modifiziert wurde und eine 0 korrekt ist oder ob bei der Validierung darauf reagiert werden soll.

Mir persönlich ist aber der Wert „None“ nicht als eine geometrische Form bekannt.

Ferner wird es problematisch, falls man das Enum mal in der GUI angezeigt haben möchte.

„VerticalCylinder“ sieht in der UI ziemlich seltsam aus. Besser wäre natürlich „Vertical cylinder“ oder auch (in der deutschen Version) „Vertikaler Zylinder“.

Also muss hier wieder ein Mapping hier. Ein switch-case oder auch ein Dictionary.

Übrigens, wusstest du bereits folgendes?

Shape geo1 = (Shape)1; // -> Shape.Cuboid
Shape geo2 = (Shape)4; // -> 4

Oder, was passiert mit dem folgenden code, wenn eine neue Anforderung kommt?

switch (shape)
{
    case Shape.Cuboid:
        HandleCuboids(geo);
    case Shape.VerticalCylinder:
        HandleVerticalCylinders(geo);
    case Shape.HorizontalCylinder:
        HandleHorizontalCylinders(geo);
    default:
        throw new UnknownGeometryShapeException();
}

Beim schreiben erwartet man nämlich, dass man in den default fall nur kommt, wenn z.B. „None“ oder „Unknown“ als Wert steht.

Werden jetzt aber Kugeln oder Kegeln gefordert, macht diese Stelle da oben eine Grätsche. Aber eine gewalltige.

Wer denkt den bitte schon daran ALLE Code Stellen anzupassen, wenn man nur einen weiteren trivialen Wert einem enum hinzufügt?

Und das hoffentlich am Ende der Auflistung. Nicht, dass da noch jemand auf die Idee gekommen ist, die Werte als Zahl zu speichern und später wieder in ein enum zu konvertieren. Dann ist da nähmlich die (du weißt schon was) am Dampfen 🙂

Alternative zum Enum

Wann auch immer ich an so einem Punkt angekommen war, stand ich vor der Entscheidung. Entweder doch ein enum machen und später mit einem switch-case auf die Werte zurückgreifen oder doch statische Klasse dafür zu verwenden.

Diese sahen bei mir meist wie folgt aus:

public class Shape
{
   public static string Cuboid => "Quader";
   public static string VerticalCylinder => "Vertikaler Zylinder";
   public static string HorizontalCylinder => "Horizontaler Zylinder";
}

Nichts wildes, erfüllte aber den Zweck. Und der zweck heiligt ja bekanntlich die Mittel 🙂

Egal welche entscheidung ich jedoch getroffen habe, ich war nie so richtig mit meiner Entscheidung zufrieden gewesen.

Der Grund dafür ist ein einfacher. Ich habe zwar einiges an Flexibilität gewonnen, jedoch alle Vorteile von einem Enum verloren. Sag was du willst, aber ein Enum ist nichts desto trotz praktisch.

Ein Enum in der Klasse

Mehr oder weniger durch Zufall bin ich auf den Artikel von LosTechies gekommen, indem genau diese Problematik behandelt wird.

Und was soll ich sagen? Es war eine Inspiration für ein neues kleines Werkzeug in meiner Sammlung.

Mit diesem kleinen, aber feinen Helfer, können diese Art von Enums genauso genutzt werden wie ein normales Enum auch.

Allerdings verändert sich ein wenig die Definition. Am Ende sieht es wie folgt aus:

public class Shape : Enum<Shape, int>
{
   public static Shape Cuboid => new Shape(1, "Quader");
   public static Shape VerticalCylinder => new Shape(2, "Vertikaler Zylinder");
   public static Shape HorizontalCylinder => new Shape(3, "Horizontaler Zylinder");

   protected Shape(int value, string name) : base(value, name) { }
}

Wenn man beide Augen zudrückt, kann man damit sogar von einem richtigen Enum in das Klassen-Enum konvertieren und anders herum.

FooEnum one = (FooEnum)(int)BarEnum.One;
BarEnum two = (BarEnum)(int)FooEnum.Two;

Dadurch, dass es sich hierbei um eine Klasse handelt, lässt sich auch eine kleine Logik mit einbringen.

Den Quellcode dazu gibt es bei github unter:
https://github.com/serkuk/ToolKit/blob/master/KukSoft.ToolKit.Core/DataTypes/Enum.cs

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.