2012-03-26 8 views
5

Um Enums in Kombination mit Strings verwenden zu können, habe ich eine StringEnum-Klasse basierend auf https://stackoverflow.com/a/424414/1293385 implementiert.Casting-String zu type-safe-enum mit benutzerdefinierter Konvertierung

Allerdings habe ich Probleme, wenn ich versuche, die vorgeschlagenen benutzerdefinierten Konvertierungsoperationen zu implementieren.

Die StringEnum Klasse ist wie folgt definiert:

public abstract class StringEnum 
{ 
    private readonly String name; 
    private readonly int value; 

    protected static Dictionary<string, StringEnum> instances 
     = new Dictionary<string, StringEnum>(); 

    protected StringEnum(int value, string name) 
    { 
     this.value = value; 
     this.name = name; 
     instances.Add(name.ToLower(), this); 
    } 

    public static explicit operator StringEnum(string name) 
    { 
     StringEnum se; 
     if (instances.TryGetValue(name.ToLower(), out se)) 
     { 
      return se; 
     } 
     throw new InvalidCastException(); 
    } 

    public override string ToString() 
    { 
     return name; 
    } 
} 

Ich benutze diese Klasse als Basis wie folgt aus:

public class DerivedStringEnum : StringEnum 
{ 
    public static readonly DerivedStringEnum EnumValue1 
     = new DerivedStringEnum (0, "EnumValue1"); 
    public static readonly DerivedStringEnum EnumValue2 
     = new DerivedStringEnum (1, "EnumValue2"); 

    private DerivedStringEnum (int value, string name) : base(value, name) { } 
} 

aber wenn ich versuche, es zu werfen mit

string s = "EnumValue1" 
DerivedStringEnum e = (DerivedStringEnum) s; 

Eine InvalidCastException wird zurückgegeben. Die Überprüfung des Codes zeigt, dass das Instanzenattribut der StringEnum-Klasse niemals gefüllt wird.

Gibt es eine einfache Möglichkeit, dies zu beheben?

Ich bevorzuge nicht C# Attribut "Magie" wie [StringValue ("EnumValue1")].

Danke!

+0

Was Sie tun gemein durch Magie? –

+4

Warum erfinden Sie dieses Rad neu? Das in System.ComponentModel definierte Beschreibungsattribut und eine einfache statische Klasse erfüllen die Aufgabe. –

+1

Nicht direkt auf die Frage/Antwort (Andras Zoltan Ich denke, ist richtig), aber das statische Wörterbuch auf StringEnum wirft rote Fahnen zu mir. Wenn Sie zwei verschiedene abgeleitete Enum-Klassen haben, aber beide einen Eintrag mit demselben "Namen" haben (z. B. Colour.Orange und Fruit.Orange), führt dies nicht zu einer Argument-Exception-Ausnahme, die dem Schlüssel bereits hinzugefügt wurde, seit dem Dictionary wird statisch geteilt? Es scheint mir, dass das Wörterbuch bei jeder Implementierung neu deklariert werden sollte oder vielleicht die Typinformation zusammen mit dem Namen beim Erstellen/Nachschlagen des Schlüssels enthalten sollte. –

Antwort

6

Sie müssen auch einen expliziten Cast-Operator für die abgeleitete Klasse definieren. Von der Basisklasse wird nicht erwartet, dass sie in eine abgeleitete Klasse umwandeln kann.

Da Operatoren statisch sind, werden sie nicht vererbt - der explizite Umwandlungsoperator ist nur zwischen string und StringEnum definiert. Sie können dies tun, eher hässlich doppelt werfen sich:

DerivedStringEnum e = (DerivedStringEnum)(StringEnum)s 

Oder in der abgeleiteten Klasse können Sie setzen: (bearbeitet nach @ili meine eigene Aufsicht hingewiesen)

public static explicit operator DerivedStringEnum(string name) 
{ 
    return (DerivedStringEnum)(StringEnum)name; 
} 
+0

'public static expliziter Operator DerivedStringEnum (Zeichenfolgename) { return (DerivedStringEnum) (StringEnum) name; } 'würde nicht funktionieren? – ili

+0

lol wahr - oh Liebling, wie konnte ich das verpasst haben! Zu meiner Verteidigung - kann ich nur sagen, dass ich seit 11 Stunden auf Arbeit war: $ –

+0

Kaum gestartet dann! :( –

1

Sie nicht tun müssen Sie einen weiteren Operator hinzufügen. Sie haben das tatsächliche Problem bereits identifiziert:

Überprüfung des Codes zeigt, dass das Instanzenattribut der StringEnum-Klasse nie gefüllt wird.

Das ist, weil nichts die Klasse DerivedStringEnum jemals initialisiert werden muss. Sie beziehen sich nie auf etwas, das zwingen würde, es zu initialisieren. Wenn Sie dies tun, indem einem statischen Konstruktor hinzugefügt und eine statische Methode (um Typ-Initialisierung Optimierungen zu vermeiden), die dann die Initialisierung zu erzwingen genannt wird, es funktioniert gut:

public class DerivedStringEnum : StringEnum 
{ 
    // Other members as before. 

    static DerivedStringEnum() 
    { 
    } 

    public static void ForceInit() 
    { 
    } 
} 

class Test 
{ 
    static void Main() 
    { 
     string s = "EnumValue1"; 
     DerivedStringEnum.ForceInit(); 
     DerivedStringEnum e = (DerivedStringEnum) s; 
     Console.WriteLine(e); // EnumValue1 
    } 
} 

Es ist nicht etwas, was ich tun würde empfehlen - Ich mag es nicht, wenn der Zustand einer Basisklasse effektiv davon abhängt, ob ein abgeleiteter Typ initialisiert wurde - aber es erklärt Dinge ...

Beachten Sie, dass die Antwort von Andras funktioniert (oder zumindest kann funktionieren, obwohl Ich glaube nicht, dass es garantiert ist), weil Sie durch Aufrufen des im abgeleiteten Typ deklarierten Operators init initialisieren können ializing den Typ.Sie könnten nicht - type initialization can be very lazy. Ich glaube, dass Sie tatsächlich ein Feld innerhalb des Operators (vor dem Aufruf des Basistransformationsoperators) verwenden müssen, um die Initialisierung wirklich zu erzwingen.

Mit nur der StringEnum Betreiber gemäß der ursprünglichen Frage, dieser Zeile:

DerivedStringEnum e = (DerivedStringEnum) s; 

kompiliert wird sowohl dem benutzerdefinierten Operator Aufruf und ein gegossenes:

IL_000d: ldloc.0 
IL_000e: call  class StringEnum StringEnum::op_Explicit(string) 
IL_0013: castclass DerivedStringEnum