Преобразование строки в type-safe-enum с использованием пользовательского преобразования

Чтобы использовать Enum в сочетании со строками, я реализовал класс StringEnum на основеhttps://stackoverflow.com/a/424414/1293385.

Однако я сталкиваюсь с проблемами, когда пытаюсь реализовать предложенные пользователем операции преобразования.

Класс StringEnum определяется следующим образом:

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

Я использую этот класс как базу:

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

Однако, когда я пытаюсь разыграть его, используя

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

InvalidCastException возвращается. Проверка кода показывает, что атрибут экземпляров класса StringEnum никогда не заполняется.

Есть ли простой способ это исправить?

Я предпочитаю не использовать атрибут магии C #, такой как [StringValue ("EnumValue1")].

Спасибо!

 Chris Sinclair26 мар. 2012 г., 18:12
Не связано напрямую с вопросом / ответом (Андрас Золтан, я думаю, это правильно), но этот статический словарь на StringEnum выдает мне красные флажки. Если у вас есть два разных производных класса enum, но у них обоих есть запись с одним и тем же «именем» (например, Colour.Orange и Fruit.Orange), это не вызовет исключение ArgumentException, уже добавленное ключом, так как словарь статически поделился? Мне кажется, что словарь должен быть повторно объявлен для каждой реализации или, возможно, включать информацию о типе вместе с именем при создании / поиске ключа.
 Andras Zoltan26 мар. 2012 г., 18:14
@ChrisSinclair Я думал о том же - но я избегал этого. И еще есть потенциальная проблема с безопасностью потоков.
 Tony Hopkinson26 мар. 2012 г., 18:10
Почему ты изобретаешь это колесо? Атрибут Description, определенный в System.ComponentModel, и простой статический класс сделают эту работу.
 ili26 мар. 2012 г., 18:15
Dictionary<Type, Dictionary<String, EnumString>> лучше использовать для производных классов ... также лучше сделать такой метод безопасным для потока.
 Yuriy Faktorovich26 мар. 2012 г., 18:03
Что вы подразумеваете под магией?

Ответы на вопрос(2)

не нужно добавить еще один оператор. уже определили реальную проблему:

Проверка кода показывает, что атрибут экземпляров класса StringEnum никогда не заполняется.

Это потому, что ничто не заставляетDerivedStringEnum класс для инициализации. никогда не имеете в виду ничего, чтобыло бы заставить его инициализироваться. Если вы сделаете это, добавив статический конструктор (чтобы избежать оптимизации инициализации типа) и статический метод, который затем вызывается для инициализации, он работает нормально:

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

Это не то, что я бы рекомендовал делать - мне не нравится, когда состояние базового класса эффективно зависит от того, был ли инициализирован какой-либо производный тип - но это действительно объясняет вещи ...

Обратите внимание, что ответ Андрас работает (или, по крайней мере,Можно работать, хотя я не думаю, что это гарантировано) потому что, вызывая оператор, объявленный в производном типе, вымог бы в конечном итоге инициализация типа. не могли бы хотя -инициализация типа может быть очень ленивой, Я полагаю, что вам придется использоватьполе внутри оператора (до вызова оператора базового преобразования), чтобы действительно вызвать инициализацию.

С участиемтолько что StringEnum оператор в соответствии с первоначальным вопросом, эта строка:

DerivedStringEnum e = (DerivedStringEnum) s;

компилируется как для вызова пользовательского оператораа также в ролях:

IL_000d:  ldloc.0
IL_000e:  call       class StringEnum StringEnum::op_Explicit(string)
IL_0013:  castclass  DerivedStringEnum
Решение Вопроса

ассу. Базовый класс не должен знать, как привести к производному классу.

Так как операторы являются статическими, они не наследуются - явный оператор приведения определяется только междуstring а такжеStringEnum, Вы можете сделать это довольно уродливо, дважды сыграв себя:

DerivedStringEnum e = (DerivedStringEnum)(StringEnum)s

Или в вашем производном классе вы можете поместить: (отредактировано после того, как @ili указал на мой собственный надзор)

public static explicit operator DerivedStringEnum(string name) 
{ 
  return (DerivedStringEnum)(StringEnum)name; 
} 
 Tony Hopkinson26 мар. 2012 г., 18:57
Едва началось тогда! :(
 Andras Zoltan27 мар. 2012 г., 22:21
Нет проблем; и я согласен: это, наверное, лучший путь вперед :)
 Andras Zoltan26 мар. 2012 г., 21:55
@ TonyHopkinson действительно! И с кофеином позади меня нет программы, которую я не могу полностью испортить!
 Andras Zoltan26 мар. 2012 г., 18:22
лол правда - о боже, как я мог пропустить это! В свою защиту - могу ли я сказать, что я уже 11 часов на работе: $
 ili26 мар. 2012 г., 18:16
public static explicit operator DerivedStringEnum(string name) { return (DerivedStringEnum)(StringEnum)name; } не будет работать?

Ваш ответ на вопрос