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

Чтобы использовать 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")].

Спасибо!

Ответы [ 2 ]

6 голосов
/ 26 марта 2012

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

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

DerivedStringEnum e = (DerivedStringEnum)(StringEnum)s

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

public static explicit operator DerivedStringEnum(string name) 
{ 
  return (DerivedStringEnum)(StringEnum)name; 
} 
1 голос
/ 29 июля 2013

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

Проверка кода показывает, что атрибут экземпляров класса 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;

скомпилирована как для вызова пользовательского оператора , так и cast:

IL_000d:  ldloc.0
IL_000e:  call       class StringEnum StringEnum::op_Explicit(string)
IL_0013:  castclass  DerivedStringEnum
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...