Die Sache mit der Enumeration

Zugriffe: 3953 starstarstarstarstarstarstarstarstarstar Bewertung:2,33 (3 Bewertungen) 23.01.2011

Es kommt vor, dass man bestimmte Entscheidungen zu einzelnen Abläufen in der Softwareentwicklung von festen Parametern abhängig machen muss. Die Schwierigkeit besteht oft darin, welche Wertebereiche man diesen Parametern zu Grunde legen sollte. Es bietet sich an, hier einen numerischen Wert zu wählen, gleich sie schlecht lesbar sind. Wer möchte genau sagen können, was denn der folgende Methodenaufruf bedeuten mag:

MachWas(2);
MachWas(4);

An dieser Stelle wird klar, dass die Lesbarkeit deutlich darunter leidet. Nun könnte wer den Einwand erheben und sagen: dann schreib doch einfach Klartext.
Dann hätte man so etwas:

MachWas("später");
MachWas("SAETER");

Auch hier wird die Problematik schnell offensichtlich - Schreibfehler. Zudem kommt hinzu, dass der eine Entwickler in Unicode verliebt ist, der andere der Auffassung ist, dass dies immer problematisch ist und daher Umlaute ausschreibt.

Hier könnte man jetzt Enumerationen zum Einsatz bringen. Im Folgenden möchte ich die Möglichkeiten näher darstellen aber auch die Fehlerproblematik aufzeigen, die sich durch den Einsatz von Enumerationen ergeben kann.
Enumerationen sollten meines Erachtens immer eingesetzt werden, sobald es sich anbietet. Man kommt bisweilen aber um verschiedene Diskussionen nicht herum. Steigt man in ein gewachsenes Projekt ein, so kann man nicht daher kommen und alles einfach umändern. Man kann aber Brücken zum Erfolg und damit zu besserer Softwarequalität bauen.

Nehme ich mal das oben genannte Beispiel, wo im Methodenaufruf eine Zeichenkette übergeben wird. Hier ist die Argumentation ganz klar die Fehleranfälligkeit durch Schreibfehler. Das gilt es zu verhindern - erst Recht, wenn derartige Aufrufe mehrfach an verschiedenen Stellen im Projekt vorkommen. Andererseits sollte man nicht wie die Axt im Walde vorgehen, denn bei gewachsenen Anwendungen (auch wenn es Brownfieldprojekte sind) muss man bedenken, dass die Methodenlogik dennoch gewissermaßen getestet ist. Ziel sollte daher im ersten Schritt sein, nur die Methodendefinition anzupassen.

Als erstes erstelle ich hierzu eine Enumeration.

public enum EnumMachWas
{
        SOFORT,
        GLEICH,
        EVENTUELL,
        SPAETER,
        NIE,
}

Im nächsten Schritt erstelle ich eine weitere Überladung der Methode MachWas.

void MachWas(EnumMachWas wasDenn)
{
       MachWas(wasDenn.ToString());
}

Nun wird deutlich, dass dies nicht wirklich funktionieren kann, hat doch das Eingangs gezeigte Beispiel gezeigt, dass im Fall von EnumMachWas.SPAETER der Wert "später" erwartet wird. Der Aufruf der ToString()-Methode ergibt aber "SPAETER". Der schnellste Weg wäre hier über eine switch-case-Anweisung zu gehen:

MachWasSwitch

Aus meiner Sicht ist auch hier wieder problematisch, dass dieses Vorgehen sich an mehreren Stellen im Code wiederholen kann und das Problem des potentiellen Schreibfehlers fortbesteht.

Es wäre doch schön, wenn ich der Enumeration einfach per Definition einen bestimmten Wert zuweisen könnte, so nach dem Muster:

EnumMachWasString

Spätestens beim kompilieren erfahren wir, dass dies so nicht geht. Man ist geneigt, per Definition die Enumeration vom Type String abzuleiten. Aber auch das wird beim kompilieren mit einer Fehlermeldung quittiert:

"Type byte, sbyte, short, ushort, int, uint, long, or ulong expected"

Doch eine Lösung muss es doch geben - oder?

Wer sich schon einmal auf die Suche nach einer passenden Lösung gemacht hat, bekommt über die Suchmaschine seiner Wahl als erstes die Fragestellung zu lesen: "Wie kann ich die ToString()-Methode einer Enumeration überschreiben?". Leider lautet die Antwort hierzu: gar nicht - es geht einfach nicht.

Wie sieht die Sache aber eigentlich mit Attributen aus - es sollte doch möglich sein, einem jeden Field der Enumeration ein eigenes Attribute zu verpassen. Wenn dies möglich ist, könnte man per Attributdefinition einen weiteren Wert z.B. vom Type String zuweisen.

Bei der Definition einer eigenen Attributeklasse legt man in der Regel fest, wo dieses zulässig sein soll. Dazu wird der Attributeklasse das Attribute AttributeUsage zugewiesen. Über AttributeTargets wird dann deutlich, dass es möglich sein muss, Enumerationen mit Attributen zu versehen, da Enum als auch Field als AttributeTargets angegeben werden kann. Da eine Enumeration erfahrungsgemäß immer aus mehr als einem Wert besteht, muss auch AllowMultiple auf true gesetzt werden.

Eine einfache Attributeklasse schaut demnach wie folgt aus:

EnumMachWasAttribute

Jetzt müssen den einzelnen Fields der Enumeration die Attribute zugewiesen werden.

AttributeTextZuweisung

Damit hat sich die Definition der Enumeration EnumMachWas entsprechend geändert.

AttributeZuweisungKomplett

Doch wie erhalte ich jetzt die Attributewerte? Hier hilft eine Extension Methods weiter.

ExtEnumMachWas

Das Beispiel ist an dieser Stelle bewusst etwas einfach gehalten. Hier ist alles auf nur einem Attribute eines bestimmten Attributetypes und auf die Eigenschaft Text festgelegt. Es empfielt sich an dieser Stelle, eine generische Methode zu definieren und hinsichtlich der Eigenschaften wieder auf Enumerationen zurückzugreifen. Im ersten Schritt geht es jetzt erst einmal nur um das generelle Prinzip, was mit dem o.g. Beispiel deutlich wird.

Wenn man jetzt auf den Attributewert der Enumeration EnumMachWas zugreifen möchte, braucht man nur noch die Methode GetAttribute() aufzurufen.

ResponseEnumMachWas

Jetzt werden die Vorteile des Einsatzes von Enumerationen wieder deutlich: man kann an einer zentralen Stelle diese definieren und ohne größeren Umbau in gewachsene Projekte einführen. Gerade im Rahmen der komponentenorientierten Entwicklung ist es unerlässlich, Codeduplizierungen zu vermeiden - Enumerationen sind dabei eine sehr gute Unterstützung. Weiter unterstützen sie bei der Vermeidung von Schreibfehlern und den damit verbundenen Schwierigkeiten.

Bei aller Euphorie können aber immer noch Probleme in der Verwendung von Enumerationen auftreten. Was passiert bei folgender Definition:

MachWasBug

Möchte ich jetzt nachstehenden Aufruf umsetzen,

MachWasBugOut

so erhalte ich folgende fehlerhafte Ausgabe:

FehlerAusgabe

Doch warum ist das so? Der Fehler liegt wohl in der internen Verarbeitung der Enumeration. Es bleibt zu hoffen, dass dies in einer der nächsten Frameworkversionen behoben sein wird.

Im Fazit heißt es, sobald Werte den Fielddefinitionen zugewiesen werden, sollten entweder alle Felder einen Wert zugewiesen bekommen oder nach dem FieldValue sortiert werden. Alternativ könnte man aber auch hier wieder auf Attribute zurückgreifen.

Im Ergebnis kann man aber sagen, dass Enumerationen die Arbeit deutlich unterstützen können - nicht zu Letzt, da sie maßgeblich zur Lesbarkeit des Codes (auch nach Jahren) beitragen. Durch die Verwendung von Attributen kann man Enumerationen recht vielseitig einsetzen, ohne dabei auf dem der Enumeration typischen Wertebereich beschränkt zu sein.


7 Kommentare
Patrick Patrick Sonntag, 23. Januar 2011
Hallo René,

evtl. hilft Dir hier die Statische Methode GetName bzw GetNames der Klasse Enum weiter..
Das Beispiel dazu aus der Dot.Net Hilfe:
public class GetNameTest {
enum Colors { Red, Green, Blue, Yellow };
enum Styles { Plaid, Striped, Tartan, Corduroy };

public static void Main() {

Console.WriteLine("The 4th value of the Colors Enum is {0}", Enum.GetName(typeof(Colors), 3));
Console.WriteLine("The 4th value of the Styles Enum is {0}", Enum.GetName(typeof(Styles), 3));
}
}

public class GetNamesTest {
enum Colors { Red, Green, Blue, Yellow };
enum Styles { Plaid, Striped, Tartan, Corduroy };

public static void Main() {

Console.WriteLine("The values of the Colors Enum are:");
foreach(string s in Enum.GetNames(typeof(Colors)))
Console.WriteLine(s);

Console.WriteLine();

Console.WriteLine("The values of the Styles Enum are:");
foreach(string s in Enum.GetNames(typeof(Styles)))
Console.WriteLine(s);
}
}

Grüße
Patrick
Rene Rene Sonntag, 23. Januar 2011
Hallo Patrick,

das löst das Problem in der Sache aber nicht, so dass Attribute aus meiner Sicht wesentlich flexibler und zuverlässiger sind. Auch bei der Wertzuweisung bekomme ich mit deinem Beispiel nicht das erwartete Ergebnis - ich denke, es ist schlichtweg ein Fehler in der Umsetzung/Interpretation der Enumeration seitens des Frameworks.

Ich habe weniger Probleme, seit ich mich dabei auf Attribute verlasse.

Gruß
Rene
Thomas Sczyrba Thomas Sczyrba Montag, 24. Januar 2011
Hi René,
toller Beitrag - was ich mir generell noch wünschen würde ist das die Codefragmente nicht als Pic sondern als Text im Blog stehen - hierfür gibt es tolle Plug Ins.

Viele Grüße,
Thomas Sczyrba
Mario Noack Mario Noack Montag, 24. Januar 2011
Ich kann hier keinen Fehler im Framework erkennen. Die Doku sagt ja eindeutig aus:
> Zur Kompilierzeit werden alle Verweise auf die einzelnen Werte einer Enumeration in numerische Literale konvertiert
Somit ist das Ergebnis völlig korrekt und systembedingt nicht anders lösbar.
Rene Drescher-Hackel Rene Drescher-Hackel Montag, 24. Januar 2011
@Mario: Wenn du dir mal anschaust, was zur Laufzeit da raus kommt, sollte es schon gehen, dass zugewiesene Werte berücksichtigt werden.

@Thomas: ich bin dabei, mir fehlt aber etwas die Zeit im Moment. Das Plugin sollte ja W3C-kompatibel sein, das will alles getestet sein ;-). Das entsprechende Script hab ich schon da...

Gruß
Rene
Rainer Hilmer Rainer Hilmer Samstag, 29. Januar 2011
Hallo Rene,
ich bin vor kurzem auf eine tolle Sache gestoßen, die ich hier weitergeben möchte:
Mit Hilfe eines Dictionaries kannst du jede Enumeration transformieren. Als key nimmst du die Enumeration und als value das was herauskommen soll. In deinem Beispiel wäre das ein String.
Weitere Infos mit Code-Samples findest du hier -> http://dotnet-forum.de/blogs/rainerhilmer/archive/2011/01/02/die-hochzeit-von-enum-und-strategy-pattern.aspx

und hier -> http://dotnet-forum.de/blogs/rainerhilmer/archive/2011/01/25/functional-dictionary-wof-252-r-braucht-man-das.aspx
Rainer Hilmer Rainer Hilmer Sonntag, 6. Februar 2011
Schade, deine Meinung dazu hätte mich schon interessiert.

Neuen Kommentar verfassen

Bestätigungscode