2012-03-26 15 views
5

Para utilizar Enum's en combinación con cadenas, implementé una clase StringEnum basada en https://stackoverflow.com/a/424414/1293385.Cadena de conversión a type-safe-enum utilizando la conversión definida por el usuario

Sin embargo, me encuentro con problemas cuando intento implementar las operaciones de conversión sugeridas definidas por el usuario.

clase

El StringEnum se define como sigue:

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; 
    } 
} 

I utilizar esta clase como una base como esto:

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) { } 
} 

Sin embargo cuando intento para lanzarlo usando

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

Se devuelve una InvalidCastException. La inspección del código muestra que el atributo instancias de la clase StringEnum nunca se completa.

¿Hay una manera fácil de arreglar esto?

Prefiero no usar el atributo C# "magic" como [StringValue ("EnumValue1")].

Gracias!

+0

Lo que hace que significa por magia? –

+4

¿Por qué estás reinventando esta rueda? El atributo de descripción definido en System.ComponentModel y una clase simple y estática harán el trabajo. –

+1

No relacionado directamente con la pregunta/respuesta (Andras Zoltan, creo que es correcto), pero ese diccionario estático en StringEnum me está levantando banderas rojas. Si tiene dos clases enum derivadas diferentes, pero ambas tienen una entrada con el mismo "nombre", (por ejemplo, Colour.Orange y Fruit.Orange) no causará una ArgumentException clave ya agregada dado que el diccionario está estáticamente compartido? Me parece que el diccionario debe redeclararse en cada implementación o incluir la información de tipo junto con el nombre cuando se construye/busca la clave. –

Respuesta

6

También debe definir un operador explícito de conversión en la clase derivada. No se espera que la clase base sepa cómo lanzar a una clase derivada.

Dado que los operadores son estáticos, no se heredan: el operador de conversión explícita solo se define entre string y StringEnum. Esto se puede hacer bastante feo doble échate:

DerivedStringEnum e = (DerivedStringEnum)(StringEnum)s 

O en su clase derivada puede poner: (editado después @ili señaló mi propia supervisión)

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

'public static operator explícito DerivedStringEnum (nombre de cadena) { return (DerivedStringEnum) (StringEnum) name; } '¿no funcionaría? – ili

+0

lol cierto - ¡oh querido, cómo podría haberme perdido eso! En mi defensa, ¿puedo decir que he estado en el trabajo durante 11 horas seguidas: $ –

+0

apenas comenzado, entonces! :( –

1

Usted no lo hacen necesita agregar otro operador. Ya ha identificado el problema real:

La inspección del código muestra que el atributo instancias de la clase StringEnum nunca se completa.

Eso es porque nada obliga a la clase DerivedStringEnum a inicializarse. Nunca se refiere a nada que sería forzarlo a inicializar. Si lo hace mediante la adición de un constructor estático (para evitar optimizaciones tipo de inicialización) y un método estático que luego se llama a forzar la inicialización, que funciona bien:

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 
    } 
} 

No es algo que yo recomiendo hacer - No me gusta cuando el estado de una clase base efectivamente depende de si algún tipo derivado se ha inicializado, pero explica las cosas ...

Tenga en cuenta que la respuesta de Andras funciona (o al menos puede trabajo, aunque No creo que esté garantizado) porque al invocar el operador declarado en el tipo derivado, podría terminar init el tipo.Sin embargo, puede que no - type initialization can be very lazy. Creo que tendrías que usar realmente un campo dentro del operador (antes de la llamada al operador de conversión base) para forzar realmente la inicialización.

Con simplemente el operador StringEnum según la pregunta original, esta línea:

DerivedStringEnum e = (DerivedStringEnum) s; 

se compila tanto a la invocación de operadora personalizada y un reparto:

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