Строковое представление Enum


912

У меня есть следующее перечисление:

public enum AuthenticationMethod
{
    FORMS = 1,
    WINDOWSAUTHENTICATION = 2,
    SINGLESIGNON = 3
}

Проблема, однако, в том, что мне нужно слово «FORMS», когда я спрашиваю AuthenticationMethod.FORMS, а не id 1.

Я нашел следующее решение этой проблемы ( ссылка ):

Сначала мне нужно создать собственный атрибут с именем «StringValue»:

public class StringValue : System.Attribute
{
    private readonly string _value;

    public StringValue(string value)
    {
        _value = value;
    }

    public string Value
    {
        get { return _value; }
    }

}

Затем я могу добавить этот атрибут в мой перечислитель:

public enum AuthenticationMethod
{
    [StringValue("FORMS")]
    FORMS = 1,
    [StringValue("WINDOWS")]
    WINDOWSAUTHENTICATION = 2,
    [StringValue("SSO")]
    SINGLESIGNON = 3
}

И, конечно, мне нужно что-то, чтобы получить это StringValue:

public static class StringEnum
{
    public static string GetStringValue(Enum value)
    {
        string output = null;
        Type type = value.GetType();

        //Check first in our cached results...

        //Look for our 'StringValueAttribute' 

        //in the field's custom attributes

        FieldInfo fi = type.GetField(value.ToString());
        StringValue[] attrs =
           fi.GetCustomAttributes(typeof(StringValue),
                                   false) as StringValue[];
        if (attrs.Length > 0)
        {
            output = attrs[0].Value;
        }

        return output;
    }
}

Хорошо, теперь у меня есть инструменты для получения строкового значения для перечислителя. Затем я могу использовать это так:

string valueOfAuthenticationMethod = StringEnum.GetStringValue(AuthenticationMethod.FORMS);

Хорошо, теперь все это работает как шарм, но я считаю, что это большая работа. Мне было интересно, есть ли лучшее решение для этого.

Я также попробовал кое-что со словарем и статическими свойствами, но это было не лучше.


8
Ницца! Я могу использовать это для перевода значений перечисления в локализованные строки.
Ойвинд Скаар

5
Хотя вы можете найти это долго, но на самом деле это довольно гибкий способ для других вещей. Как отметил один из моих коллег, во многих случаях это можно использовать для замены помощников Enum, которые отображают коды базы данных на значения enum и т. Д.
BenAlabaster

27
MSDN рекомендует классы атрибутов суффикса суффикса «Атрибут». Так что "класс StringValueAttribute";)
Серхио

14
Я согласен с @BenAlabaster, это на самом деле довольно гибко. Кроме того, вы можете сделать это методом расширения, просто добавив thisперед Enumвашим статическим методом. Тогда вы можете сделать AuthenticationMethod.Forms.GetStringValue();
Джастин Пихони

5
Этот подход использует отражение для чтения значений атрибутов, и он очень медленный, если вам приходится многократно вызывать GetStringValue () по моему опыту. Шаблон type-safe-enum работает быстрее.
Rn222

Ответы:


868

Попробуйте тип-безопасный-enum шаблон.

public sealed class AuthenticationMethod {

    private readonly String name;
    private readonly int value;

    public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (1, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (2, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (3, "SSN");        

    private AuthenticationMethod(int value, String name){
        this.name = name;
        this.value = value;
    }

    public override String ToString(){
        return name;
    }

}

Обновление явного (или неявного) преобразования типа может быть выполнено

  • добавление статического поля с отображением

    private static readonly Dictionary<string, AuthenticationMethod> instance = new Dictionary<string,AuthenticationMethod>();
    • nb Чтобы инициализация полей «член enum» не вызывала исключение NullReferenceException при вызове конструктора экземпляра, обязательно поместите поле «Словарь» перед полями «член enum» в своем классе. Это происходит потому, что инициализаторы статических полей вызываются в порядке объявления и перед статическим конструктором, создавая странную и необходимую, но запутанную ситуацию, когда конструктор экземпляра можно вызвать до того, как все статические поля были инициализированы, и до вызова статического конструктора.
  • заполняя это отображение в конструкторе экземпляра

    instance[name] = this;
  • и добавление пользовательского оператора преобразования типов

    public static explicit operator AuthenticationMethod(string str)
    {
        AuthenticationMethod result;
        if (instance.TryGetValue(str, out result))
            return result;
        else
            throw new InvalidCastException();
    }

17
Это выглядит как перечисление, но это не перечисление. Я могу представить, что это вызывает некоторые интересные проблемы, если люди начинают пытаться сравнивать AuthenticationMethods. Вам, вероятно, нужно перегружать различные операторы равенства.
Муравей

36
@Ant: я не должен. Поскольку у нас есть только один экземпляр каждого метода AuthenticationMethod, равенство ссылок, унаследованное от Object, работает нормально.
Якуб Штурц

10
@tyriker: компилятор делает. Конструктор является закрытым, поэтому вы не можете создать новый экземпляр. Также статические члены не доступны через экземпляр.
Якуб Штурц

21
@Jakub Очень интересно. Мне пришлось поиграть с ним, чтобы понять, как его использовать, и понять его преимущества. Это открытый, нестатический класс, но его нельзя создать, а вы можете получить доступ только к его статическим членам. По сути, он ведет себя как перечисление. Но лучшая часть ... статические члены типизируются классом, а не универсальной строкой или int. Это ... [подожди] ... типа безопасный enum! Спасибо, что помогли мне понять.
tyriker

6
@kiran Ниже я опубликовал слегка измененную версию ответа Якуба Штурца, которая позволяет использовать его с инструкциями Switch-Case, так что теперь у этого подхода нет недостатков :)
deadlydog

228

Используйте метод

Enum.GetName(Type MyEnumType,  object enumvariable)  

как в (Предположим Shipper, это определенный Enum)

Shipper x = Shipper.FederalExpress;
string s = Enum.GetName(typeof(Shipper), x);

Есть множество других статических методов в классе Enum, которые тоже стоит исследовать ...


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

5
Enum.GetName отражает имена полей в перечислении - так же, как .ToString (). Если производительность является проблемой, это может быть проблемой. Я бы не беспокоился об этом, если вы не конвертируете множество перечислений.
Кит

8
Другой вариант, который нужно учитывать, если вам нужно enum с дополнительной функциональностью, - это «свернуть свой собственный», используя структуру ... вы добавляете статические свойства с именем только для чтения, чтобы представлять значения перечисления, которые инициализируются для конструкторов, которые генерируют отдельные экземпляры структуры. ...
Чарльз Бретана

1
затем вы можете добавить любые другие члены структуры, какие пожелаете, для реализации любой функциональности, которую вы хотите, чтобы это "перечисление" имело ...
Чарльз Бретана,

2
Проблема в том, что GetName не локализуем. Это не всегда проблема, но об этом нужно знать.
Джоэл Коухорн

79

Вы можете ссылаться на имя, а не на значение, используя ToString ()

Console.WriteLine("Auth method: {0}", AuthenticationMethod.Forms.ToString());

Документация здесь:

http://msdn.microsoft.com/en-us/library/16c1xs4z.aspx

... и если вы называете свои перечисления в Pascal Case (как я, например ThisIsMyEnumValue = 1 и т. д.), вы можете использовать очень простое регулярное выражение для печати дружественной формы:

static string ToFriendlyCase(this string EnumString)
{
    return Regex.Replace(EnumString, "(?!^)([A-Z])", " $1");
}

который можно легко вызвать из любой строки:

Console.WriteLine("ConvertMyCrazyPascalCaseSentenceToFriendlyCase".ToFriendlyCase());

Выходы:

Преобразование моего сумасшедшего предложения по делу Паскаля в дружественное дело

Это избавляет от необходимости обходить дома, создавая собственные атрибуты и прикрепляя их к своим перечислениям или используя таблицы поиска, чтобы объединить значение перечисления с дружественной строкой и, что лучше всего, оно самоуправляется и может использоваться для любой строки Pascal Case, которая бесконечно более многоразового использования. Конечно, это не позволяет вам иметь другой дружественное имя, от вашего enum, которое предоставляет ваше решение.

Мне нравится ваше оригинальное решение, хотя для более сложных сценариев, хотя. Вы можете продвинуть свое решение на один шаг вперед и сделать свой GetStringValue методом расширения вашего перечисления, и тогда вам не нужно будет ссылаться на него как StringEnum.GetStringValue ...

public static string GetStringValue(this AuthenticationMethod value)
{
  string output = null;
  Type type = value.GetType();
  FieldInfo fi = type.GetField(value.ToString());
  StringValue[] attrs = fi.GetCustomAttributes(typeof(StringValue), false) as StringValue[];
  if (attrs.Length > 0)
    output = attrs[0].Value;
  return output;
}

Вы можете легко получить к нему доступ прямо из вашего экземпляра enum:

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());

2
Это не поможет, если «дружественному имени» нужен пробел. Такие как «Аутентификация по формам»
Рэй Бойсен

4
Поэтому убедитесь, что перечисление названо заглавными буквами, например FormsAuthentication, и вставьте пробел перед любыми заглавными буквами, которых нет в начале. Это не ракетостроение, чтобы вставить пробел в строку ...
BenAlabaster

4
Автоматический пробел имен Pascal Case становится проблематичным, если они содержат сокращения, которые должны быть написаны заглавными буквами, например, XML или GPS.
Ричард Эв

2
@RichardEv, нет идеального регулярного выражения для этого, но вот тот, который должен работать немного лучше с сокращениями. "(?!^)([^A-Z])([A-Z])", "$1 $2", Так HereIsATESTстановится Here Is ATEST.
резервные байты

Не изящно делать эти маленькие "хаки", какие они есть. Я понимаю, что говорит OP, и я пытаюсь найти аналогичное решение, то есть используя элегантность Enums, но имея возможность легко получить доступ к связанному сообщению. Единственное решение, которое я могу придумать, - это применить какое-то отображение между именем enum и строковым значением, но это не решает проблему поддержки строковых данных (однако делает это практичным для сценариев, где вам нужно иметь несколько областей и т. Д.). )
Таир Халид

72

К сожалению, размышления о получении атрибутов в перечислениях довольно медленные:

Посмотрите на этот вопрос: Кто-нибудь знает быстрый способ получить пользовательские атрибуты для значения перечисления?

.ToString()Довольно медленно на перечислениях тоже.

Вы можете написать методы расширения для перечислений:

public static string GetName( this MyEnum input ) {
    switch ( input ) {
        case MyEnum.WINDOWSAUTHENTICATION:
            return "Windows";
        //and so on
    }
}

Это не очень хорошо, но будет быстрым и не потребует отражения для атрибутов или имени поля.


Обновление C # 6

Если вы можете использовать C # 6, тогда новый nameofоператор работает для перечислений, поэтому nameof(MyEnum.WINDOWSAUTHENTICATION)он будет преобразован "WINDOWSAUTHENTICATION"во время компиляции , что сделает его самым быстрым способом получения имен перечислений.

Обратите внимание, что это преобразует явное перечисление во встроенную константу, поэтому оно не работает для перечислений, которые есть в переменной. Так:

nameof(AuthenticationMethod.FORMS) == "FORMS"

Но...

var myMethod = AuthenticationMethod.FORMS;
nameof(myMethod) == "myMethod"

24
Вы можете получить значения атрибутов один раз и поместить их в словарь <MyEnum, строка>, чтобы сохранить декларативный аспект.
Джон Скит

1
Да, это то, что мы сделали в приложении с множеством перечислений, когда узнали, что отражение было узким местом.
Кит

Спасибо Джону и Кита, в итоге я воспользовался твоим предложением по словарю. Прекрасно работает (и быстро!).
Хельге Кляйн

@JonSkeet Я знаю, что это старый. Но как этого добиться?
user919426

2
@ user919426: Достичь хочешь? Положить их в словарь? Просто создайте словарь, в идеале с инициализатором коллекции ... не понятно, о чем вы просите.
Джон Скит

59

Я использую метод расширения:

public static class AttributesHelperExtension
    {
        public static string ToDescription(this Enum value)
        {
            var da = (DescriptionAttribute[])(value.GetType().GetField(value.ToString())).GetCustomAttributes(typeof(DescriptionAttribute), false);
            return da.Length > 0 ? da[0].Description : value.ToString();
        }
}

Теперь украсить с enumпомощью:

public enum AuthenticationMethod
{
    [Description("FORMS")]
    FORMS = 1,
    [Description("WINDOWSAUTHENTICATION")]
    WINDOWSAUTHENTICATION = 2,
    [Description("SINGLESIGNON ")]
    SINGLESIGNON = 3
}

Когда вы звоните

AuthenticationMethod.FORMS.ToDescription()вы получите "FORMS".


1
Я должен был также добавить using System.ComponentModel;, что этот метод работает, только если вы хотите, чтобы значение String совпадало с именем Enum. ОП хотел другое значение.
elcool

2
Разве вы не имеете в виду, когда вы звоните AuthenticationMethod.FORMS.ToDescription()?
nicodemus13

41

Просто используйте ToString()метод

public enum any{Tomato=0,Melon,Watermelon}

Для ссылки на строку Tomatoпросто используйте

any.Tomato.ToString();

Ух ты. Это было легко. Я знаю, что ОП хотел добавить пользовательские описания строк, но это то, что мне было нужно. Я должен был знать, чтобы попробовать это, оглядываясь назад, но я пошел по маршруту Enum.GetName.
Rafe

7
Почему все остальные слишком усложняют это?
Брент

18
@Brent Потому что чаще всего у вас есть .ToString()значение, отличное от того, которое вам нужно.
Novitchi S

2
@ Брент - потому что это отличается от задаваемого вопроса. Вопрос, который задают, заключается в том, как получить эту строку из переменной, которой было присвоено перечисляемое значение. Это динамично во время выполнения. Это проверка определения типа и установки во время выполнения.
Хоган

1
@Hogan - ToString () также работает с переменными: any fruit = any.Tomato; string tomato = fruit.ToString();
LiborV,

29

Очень простое решение для этого с .Net 4.0 и выше. Никакого другого кода не требуется.

public enum MyStatus
{
    Active = 1,
    Archived = 2
}

Чтобы получить строку о просто использовать:

MyStatus.Active.ToString("f");

или

MyStatus.Archived.ToString("f");`

Значение будет «Активно» или «Архивировано».

Чтобы увидеть различные строковые форматы ("f" сверху) при вызове, Enum.ToStringпосмотрите эту страницу Строки формата перечисления


28

Я использую атрибут Description из пространства имен System.ComponentModel. Просто украсьте перечисление, а затем используйте этот код для его получения:

public static string GetDescription<T>(this object enumerationValue)
            where T : struct
        {
            Type type = enumerationValue.GetType();
            if (!type.IsEnum)
            {
                throw new ArgumentException("EnumerationValue must be of Enum type", "enumerationValue");
            }

            //Tries to find a DescriptionAttribute for a potential friendly name
            //for the enum
            MemberInfo[] memberInfo = type.GetMember(enumerationValue.ToString());
            if (memberInfo != null && memberInfo.Length > 0)
            {
                object[] attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0)
                {
                    //Pull out the description value
                    return ((DescriptionAttribute)attrs[0]).Description;
                }
            }
            //If we have no description attribute, just return the ToString of the enum
            return enumerationValue.ToString();

        }

Например:

public enum Cycle : int
{        
   [Description("Daily Cycle")]
   Daily = 1,
   Weekly,
   Monthly
}

Этот код прекрасно обслуживает перечисления, в которых вам не нужно «дружественное имя», и возвращает только .ToString () перечисления.


27

Мне очень нравится ответ Якуба Штурца, но его недостаток в том, что вы не можете использовать его с оператором switch-case. Вот слегка измененная версия его ответа, которую можно использовать с оператором switch:

public sealed class AuthenticationMethod
{
    #region This code never needs to change.
    private readonly string _name;
    public readonly Values Value;

    private AuthenticationMethod(Values value, String name){
        this._name = name;
        this.Value = value;
    }

    public override String ToString(){
        return _name;
    }
    #endregion

    public enum Values
    {
        Forms = 1,
        Windows = 2,
        SSN = 3
    }

    public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (Values.Forms, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (Values.Windows, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (Values.SSN, "SSN");
}

Таким образом, вы получите все преимущества ответа Якуба Штурца, плюс мы можем использовать его с оператором switch следующим образом:

var authenticationMethodVariable = AuthenticationMethod.FORMS;  // Set the "enum" value we want to use.
var methodName = authenticationMethodVariable.ToString();       // Get the user-friendly "name" of the "enum" value.

// Perform logic based on which "enum" value was chosen.
switch (authenticationMethodVariable.Value)
{
    case authenticationMethodVariable.Values.Forms: // Do something
        break;
    case authenticationMethodVariable.Values.Windows: // Do something
        break;
    case authenticationMethodVariable.Values.SSN: // Do something
        break;      
}

Более коротким решением было бы удалить перечисления {} и вместо этого вести статический подсчет количества созданных вами перечислений. Это также дает то преимущество, что вам не нужно добавлять новый экземпляр в список перечислений. например, public static int nextAvailable { get; private set; }тогда в конструктореthis.Value = nextAvailable++;
kjhf

Интересная идея @kjhf. Однако меня беспокоит то, что если кто-то переупорядочит код, то значение, присвоенное значениям перечисления, также может измениться. Например, это может привести к получению неверного значения перечисления при сохранении значения перечисления в файл / базу данных, изменении порядка строк «new AuthenticationMethod (...)» (например, одна удаляется), а затем снова запустить приложение и получить значение enum из файла / базы данных; значение enum может не совпадать с AuthenticationMethod, который был изначально сохранен.
deadlydog

Хороший вопрос - хотя я надеюсь, что в этих конкретных случаях люди не будут полагаться на целочисленное значение перечисления (или переупорядочивать код перечисления.) - и это значение используется исключительно как переключатель и, возможно, альтернатива .Equals () и. GetHashCode (). Если вы заинтересованы, вы всегда можете добавить огромный комментарий: «НЕ ЗАПИСАТЬ»: p
kjhf

Разве вы не можете просто перегрузить =оператора, чтобы позволить переключателю работать? Я сделал это в VB и теперь могу использовать это в select caseзаявлении.
user1318499

@ user1318499 Нет, C # имеет более строгие правила для оператора switch, чем VB. Вы не можете использовать экземпляры класса для оператора Case; Вы можете использовать только постоянные примитивы.
deadlydog

13

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

В любом случае, использование включает преобразователи типов, поэтому, если вы привязываетесь к пользовательскому интерфейсу, он «просто работает». Вы можете расширить шаблон Jakub для быстрого поиска кода путем инициализации из преобразователя типов в статические методы.

Базовое использование будет выглядеть так

[TypeConverter(typeof(CustomEnumTypeConverter<MyEnum>))]
public enum MyEnum
{
    // The custom type converter will use the description attribute
    [Description("A custom description")]
    ValueWithCustomDescription,

   // This will be exposed exactly.
   Exact
}

Ниже приведен код для пользовательского преобразователя типов enum:

public class CustomEnumTypeConverter<T> : EnumConverter
    where T : struct
{
    private static readonly Dictionary<T,string> s_toString = 
      new Dictionary<T, string>();

    private static readonly Dictionary<string, T> s_toValue = 
      new Dictionary<string, T>();

    private static bool s_isInitialized;

    static CustomEnumTypeConverter()
    {
        System.Diagnostics.Debug.Assert(typeof(T).IsEnum,
          "The custom enum class must be used with an enum type.");
    }

    public CustomEnumTypeConverter() : base(typeof(T))
    {
        if (!s_isInitialized)
        {
            Initialize();
            s_isInitialized = true;
        }
    }

    protected void Initialize()
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            string description = GetDescription(item);
            s_toString[item] = description;
            s_toValue[description] = item;
        }
    }

    private static string GetDescription(T optionValue)
    {
        var optionDescription = optionValue.ToString();
        var optionInfo = typeof(T).GetField(optionDescription);
        if (Attribute.IsDefined(optionInfo, typeof(DescriptionAttribute)))
        {
            var attribute = 
              (DescriptionAttribute)Attribute.
                 GetCustomAttribute(optionInfo, typeof(DescriptionAttribute));
            return attribute.Description;
        }
        return optionDescription;
    }

    public override object ConvertTo(ITypeDescriptorContext context, 
       System.Globalization.CultureInfo culture, 
       object value, Type destinationType)
    {
        var optionValue = (T)value;

        if (destinationType == typeof(string) && 
            s_toString.ContainsKey(optionValue))
        {
            return s_toString[optionValue];
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, 
       System.Globalization.CultureInfo culture, object value)
    {
        var stringValue = value as string;

        if (!string.IsNullOrEmpty(stringValue) && s_toValue.ContainsKey(stringValue))
        {
            return s_toValue[stringValue];
        }

        return base.ConvertFrom(context, culture, value);
    }
}

}


12

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

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

    static class AuthenticationMethod
    {
        public static readonly string
            FORMS = "Forms",
            WINDOWSAUTHENTICATION = "WindowsAuthentication";
    }

вы можете использовать тот же синтаксис, что и enum, чтобы ссылаться на него

if (bla == AuthenticationMethod.FORMS)

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


если вы используете «const» вместо «static readonly», тогда вы можете использовать значения в качестве меток регистра в выражении switch.
Эд Н.

11

Как я решил это как метод расширения:

using System.ComponentModel;
public static string GetDescription(this Enum value)
{
    var descriptionAttribute = (DescriptionAttribute)value.GetType()
        .GetField(value.ToString())
        .GetCustomAttributes(false)
        .Where(a => a is DescriptionAttribute)
        .FirstOrDefault();

    return descriptionAttribute != null ? descriptionAttribute.Description : value.ToString();
}

Enum:

public enum OrderType
{
    None = 0,
    [Description("New Card")]
    NewCard = 1,
    [Description("Reload")]
    Refill = 2
}

Использование (где o.OrderType - свойство с тем же именем, что и enum):

o.OrderType.GetDescription()

Который дает мне строку «Новая карта» или «Перезагрузить» вместо фактического значения перечисления NewCard и Refill.


Для полноты вы должны включить копию вашего класса DescriptionAttribute.
Берни Уайт

3
Берни, DescriptionAttribute находится в System.ComponentModel
agentnega

11

Обновление: посещение этой страницы, 8 лет спустя, после того, как долго не трогал C #, похоже, мой ответ больше не является лучшим решением. Мне очень нравится конвертер, связанный с атрибутами-функциями.

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


Как и большинству из вас, мне очень понравился выбранный ответ Якуба Штурца , но я также очень ненавижу копировать-вставлять код и стараюсь делать это как можно меньше.

Поэтому я решил, что мне нужен класс EnumBase, от которого большая часть функциональных возможностей унаследована / встроена, поэтому мне пришлось сосредоточиться на содержании, а не на поведении.

Основная проблема с этим подходом основана на том факте, что, хотя значения Enum являются типобезопасными экземплярами, взаимодействие происходит с статической реализацией типа Enum Class. Так что с небольшой помощью магии дженериков, я думаю, я наконец-то получил правильный микс. Надеюсь, кто-то найдет это столь же полезным, как и я.

Я начну с примера Якуба, но с использованием наследования и обобщений:

public sealed class AuthenticationMethod : EnumBase<AuthenticationMethod, int>
{
    public static readonly AuthenticationMethod FORMS =
        new AuthenticationMethod(1, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION =
        new AuthenticationMethod(2, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON =
        new AuthenticationMethod(3, "SSN");

    private AuthenticationMethod(int Value, String Name)
        : base( Value, Name ) { }
    public new static IEnumerable<AuthenticationMethod> All
    { get { return EnumBase<AuthenticationMethod, int>.All; } }
    public static explicit operator AuthenticationMethod(string str)
    { return Parse(str); }
}

А вот и базовый класс:

using System;
using System.Collections.Generic;
using System.Linq; // for the .AsEnumerable() method call

// E is the derived type-safe-enum class
// - this allows all static members to be truly unique to the specific
//   derived class
public class EnumBase<E, T> where E: EnumBase<E, T>
{
    #region Instance code
    public T Value { get; private set; }
    public string Name { get; private set; }

    protected EnumBase(T EnumValue, string Name)
    {
        Value = EnumValue;
        this.Name = Name;
        mapping.Add(Name, this);
    }

    public override string ToString() { return Name; }
    #endregion

    #region Static tools
    static private readonly Dictionary<string, EnumBase<E, T>> mapping;
    static EnumBase() { mapping = new Dictionary<string, EnumBase<E, T>>(); }
    protected static E Parse(string name)
    {
        EnumBase<E, T> result;
        if (mapping.TryGetValue(name, out result))
        {
            return (E)result;
        }

        throw new InvalidCastException();
    }
    // This is protected to force the child class to expose it's own static
    // method.
    // By recreating this static method at the derived class, static
    // initialization will be explicit, promising the mapping dictionary
    // will never be empty when this method is called.
    protected static IEnumerable<E> All
    { get { return mapping.Values.AsEnumerable().Cast<E>(); } }
    #endregion
}

Вы можете вызывать производный статический конструктор из базового статического конструктора. Я все еще смотрю на это, но до сих пор я не нашел никаких проблем с ним: stackoverflow.com/questions/55290034/…
Cory-G

10

Я согласен с Китом, но не могу голосовать (пока).

Я использую статический метод и оператор swith, чтобы вернуть именно то, что я хочу. В базе данных я храню tinyint, и мой код использует только фактическое перечисление, поэтому строки соответствуют требованиям пользовательского интерфейса. После многочисленных испытаний это привело к лучшей производительности и большей степени контроля над выходом.

public static string ToSimpleString(this enum)
{
     switch (enum)
     {
         case ComplexForms:
             return "ComplexForms";
             break;
     }
}

public static string ToFormattedString(this enum)
{
     switch (enum)
     {
         case ComplexForms:
             return "Complex Forms";
             break;
     }
}

Однако, по некоторым данным, это приводит к возможному кошмару обслуживания и некоторому запаху кода. Я стараюсь следить за длинными и многочисленными перечислениями или за часто меняющимися. В противном случае, это было отличное решение для меня.


10

Если вы пришли сюда в поисках простого «Enum», но значения которого являются строками, а не целыми, вот самое простое решение:

    public sealed class MetricValueList
    {
        public static readonly string Brand = "A4082457-D467-E111-98DC-0026B9010912";
        public static readonly string Name = "B5B5E167-D467-E111-98DC-0026B9010912";
    }

Реализация:

var someStringVariable = MetricValueList.Brand;

2
Вероятно, лучше использовать переменные consts вместо использования static readonly.
AndyGeek

1
Констант не подходит для общедоступных классов, так как они выпекаются во время компиляции, вы не можете заменить стороннюю DLL-библиотеку, не перекомпилировав весь код с консистами. Смещение производительности conts против статического readonly незначительно.
Кристиан Уильямс

7

Когда я сталкиваюсь с этой проблемой, я сначала пытаюсь найти ответы на несколько вопросов:

  • Являются ли имена моих значений enum достаточно дружелюбными для этой цели или мне нужно предоставить более дружественные?
  • Нужно ли туда-обратно? То есть мне нужно будет взять текстовые значения и разобрать их в перечисляемые значения?
  • Это то, что мне нужно сделать для многих перечислений в моем проекте, или только один?
  • Какие элементы пользовательского интерфейса я буду представлять эту информацию, в частности, буду ли я привязываться к пользовательскому интерфейсу или использовать листы свойств?
  • Это должно быть локализуемым?

Простейший способ сделать это с помощью Enum.GetValue(и поддерживать использование кругового отключения Enum.Parse). Также TypeConverter, как предполагает Стив Митчам, часто стоит создать поддержку связывания пользовательского интерфейса. (Не нужно строитьTypeConverter когда вы используете листы свойств, что является одной из приятных вещей в листах свойств. Хотя лорд знает, что у них есть свои проблемы.)

В общем, если ответы на приведенные выше вопросы показывают, что это не сработает, мой следующий шаг - создать и заполнить статический объект Dictionary<MyEnum, string>, или, возможно, a Dictionary<Type, Dictionary<int, string>>. Я склонен пропустить промежуточный шаг decorate-the-code-with-attribute, потому что следующим шагом обычно является необходимость изменения дружественных значений после развертывания (часто, но не всегда, из-за локализации).


7

Я хотел опубликовать это как комментарий к сообщению, указанному ниже, но не смог, потому что у меня недостаточно представителей - поэтому, пожалуйста, не голосуйте. Код содержал ошибку, и я хотел указать на это людям, которые пытаются использовать это решение:

[TypeConverter(typeof(CustomEnumTypeConverter(typeof(MyEnum))]
public enum MyEnum
{
  // The custom type converter will use the description attribute
  [Description("A custom description")]
  ValueWithCustomDescription,
  // This will be exposed exactly.
  Exact
}

должно быть

[TypeConverter(typeof(CustomEnumTypeConverter<MyEnum>))]
public enum MyEnum
{
  // The custom type converter will use the description attribute
  [Description("A custom description")]
  ValueWithCustomDescription,

  // This will be exposed exactly.
  Exact
}

Brillant!


5

Мой вариант

public struct Colors
{
    private String current;

    private static string red = "#ff0000";
    private static string green = "#00ff00";
    private static string blue = "#0000ff";

    private static IList<String> possibleColors; 

    public static Colors Red { get { return (Colors) red; } }
    public static Colors Green { get { return (Colors) green; } }
    public static Colors Blue { get { return (Colors) blue; } }

    static Colors()
    {
        possibleColors = new List<string>() {red, green, blue};
    }

    public static explicit operator String(Colors value)
    {
        return value.current;
    }

    public static explicit operator Colors(String value)
    {
        if (!possibleColors.Contains(value))
        {
            throw new InvalidCastException();
        }

        Colors color = new Colors();
        color.current = value;
        return color;
    }

    public static bool operator ==(Colors left, Colors right)
    {
        return left.current == right.current;
    }

    public static bool operator !=(Colors left, Colors right)
    {
        return left.current != right.current;
    }

    public bool Equals(Colors other)
    {
        return Equals(other.current, current);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (obj.GetType() != typeof(Colors)) return false;
        return Equals((Colors)obj);
    }

    public override int GetHashCode()
    {
        return (current != null ? current.GetHashCode() : 0);
    }

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

Код выглядит немного некрасиво, но использование этой структуры довольно показательно.

Colors color1 = Colors.Red;
Console.WriteLine(color1); // #ff0000

Colors color2 = (Colors) "#00ff00";
Console.WriteLine(color2); // #00ff00

// Colors color3 = "#0000ff"; // Compilation error
// String color4 = Colors.Red; // Compilation error

Colors color5 = (Colors)"#ff0000";
Console.WriteLine(color1 == color5); // True

Colors color6 = (Colors)"#00ff00";
Console.WriteLine(color1 == color6); // False

Кроме того, я думаю, что если потребуется много таких перечислений, можно использовать генерацию кода (например, T4).


4

Опция 1:

public sealed class FormsAuth
{
     public override string ToString{return "Forms Authtentication";}
}
public sealed class WindowsAuth
{
     public override string ToString{return "Windows Authtentication";}
}

public sealed class SsoAuth
{
     public override string ToString{return "SSO";}
}

а потом

object auth = new SsoAuth(); //or whatever

//...
//...
// blablabla

DoSomethingWithTheAuth(auth.ToString());

Вариант 2:

public enum AuthenticationMethod
{
        FORMS = 1,
        WINDOWSAUTHENTICATION = 2,
        SINGLESIGNON = 3
}

public class MyClass
{
    private Dictionary<AuthenticationMethod, String> map = new Dictionary<AuthenticationMethod, String>();
    public MyClass()
    {
         map.Add(AuthenticationMethod.FORMS,"Forms Authentication");
         map.Add(AuthenticationMethod.WINDOWSAUTHENTICATION ,"Windows Authentication");
         map.Add(AuthenticationMethod.SINGLESIGNON ,"SSo Authentication");
    }
}

4

Если вы думаете о проблеме, которую мы пытаемся решить, это вовсе не то, что нам нужно. Нам нужен объект, который позволяет определенному количеству значений быть связанным друг с другом; другими словами, чтобы определить класс.

Типичный для enum шаблон Jakub Šturc - лучший вариант, который я здесь вижу.

Посмотри на это:

  • Он имеет закрытый конструктор, поэтому только сам класс может определять допустимые значения.
  • Это закрытый класс, поэтому значения не могут быть изменены с помощью наследования.
  • Это безопасный тип, позволяющий вашим методам требовать только этот тип.
  • Нет доступа к производительности отражения, полученной при доступе к значениям.
  • И, наконец, его можно изменить, чтобы связать более двух полей вместе, например, Имя, Описание и Числовое значение.

4

для меня прагматический подход - класс внутри класса, пример:

public class MSEModel
{
    class WITS
    {
        public const string DATE = "5005";
        public const string TIME = "5006";
        public const string MD = "5008";
        public const string ROP = "5075";
        public const string WOB = "5073";
        public const string RPM = "7001";
... 
    }

4

Я создал базовый класс для создания строковых перечислений в .NET. Это всего лишь один файл C #, который вы можете скопировать и вставить в свои проекты или установить через пакет NuGet с именем StringEnum . GitHub Repo

  • Intellisense предложит имя перечисления, если класс помечен комментарием xml <completitionlist>. (Работает как в C #, так и в VB)

Intellisense demo

  • Использование аналогично обычному перечислению:
///<completionlist cref="HexColor"/> 
class HexColor : StringEnum<HexColor>
{
    public static readonly HexColor Blue = Create("#FF0000");
    public static readonly HexColor Green = Create("#00FF00");
    public static readonly HexColor Red = Create("#000FF");
}
    // Static Parse Method
    HexColor.Parse("#FF0000") // => HexColor.Red
    HexColor.Parse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.Parse("invalid") // => throws InvalidOperationException

    // Static TryParse method.
    HexColor.TryParse("#FF0000") // => HexColor.Red
    HexColor.TryParse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.TryParse("invalid") // => null

    // Parse and TryParse returns the preexistent instances
    object.ReferenceEquals(HexColor.Parse("#FF0000"), HexColor.Red) // => true

    // Conversion from your `StringEnum` to `string`
    string myString1 = HexColor.Red.ToString(); // => "#FF0000"
    string myString2 = HexColor.Red; // => "#FF0000" (implicit cast)

Переустановленное:

  • Вставьте следующий базовый класс StringEnum в свой проект. ( последняя версия )
  • Или установите пакет StringEnum NuGet, который основан на том, .Net Standard 1.0что он работает на .Net Core> = 1.0, .Net Framework> = 4.5, Mono> = 4.6 и т. Д.
    /// <summary>
    /// Base class for creating string-valued enums in .NET.<br/>
    /// Provides static Parse() and TryParse() methods and implicit cast to string.
    /// </summary>
    /// <example> 
    /// <code>
    /// class Color : StringEnum &lt;Color&gt;
    /// {
    ///     public static readonly Color Blue = Create("Blue");
    ///     public static readonly Color Red = Create("Red");
    ///     public static readonly Color Green = Create("Green");
    /// }
    /// </code>
    /// </example>
    /// <typeparam name="T">The string-valued enum type. (i.e. class Color : StringEnum&lt;Color&gt;)</typeparam>
    public abstract class StringEnum<T> : IEquatable<T> where T : StringEnum<T>, new()
    {
        protected string Value;
        private static Dictionary<string, T> valueDict = new Dictionary<string, T>();
        protected static T Create(string value)
        {
            if (value == null)
                return null; // the null-valued instance is null.

            var result = new T() { Value = value };
            valueDict.Add(value, result);
            return result;
        }

        public static implicit operator string(StringEnum<T> enumValue) => enumValue.Value;
        public override string ToString() => Value;

        public static bool operator !=(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value != o2?.Value;
        public static bool operator ==(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value == o2?.Value;

        public override bool Equals(object other) => this.Value.Equals((other as T)?.Value ?? (other as string));
        bool IEquatable<T>.Equals(T other) => this.Value.Equals(other.Value);
        public override int GetHashCode() => Value.GetHashCode();

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else throws InvalidOperationException.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case and takes O(log n). False allows different case but is little bit slower (O(n))</param>
        public static T Parse(string value, bool caseSensitive = true)
        {
            var result = TryParse(value, caseSensitive);
            if (result == null)
                throw new InvalidOperationException((value == null ? "null" : $"'{value}'") + $" is not a valid {typeof(T).Name}");

            return result;
        }

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else returns null.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case. False allows different case but is slower: O(n)</param>
        public static T TryParse(string value, bool caseSensitive = true)
        {
            if (value == null) return null;
            if (valueDict.Count == 0) System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // force static fields initialization
            if (caseSensitive)
            {
                if (valueDict.TryGetValue(value, out T item))
                    return item;
                else
                    return null;
            }
            else
            {
                // slower O(n) case insensitive search
                return valueDict.FirstOrDefault(f => f.Key.Equals(value, StringComparison.OrdinalIgnoreCase)).Value;
                // Why Ordinal? => https://esmithy.net/2007/10/15/why-stringcomparisonordinal-is-usually-the-right-choice/
            }
        }
    }

3

Вот еще один способ выполнить задачу связывания строк с перечислениями:

struct DATABASE {
    public enum enums {NOTCONNECTED, CONNECTED, ERROR}
    static List<string> strings =
        new List<string>() {"Not Connected", "Connected", "Error"};

    public string GetString(DATABASE.enums value) {
        return strings[(int)value];
    }
}

Этот метод называется так:

public FormMain() {
    DATABASE dbEnum;

    string enumName = dbEnum.GetString(DATABASE.enums.NOTCONNECTED);
}

Вы можете группировать связанные перечисления в их собственной структуре. Поскольку этот метод использует тип enum, вы можете использовать Intellisense для отображения списка перечислений при созданииGetString() вызова.

При желании вы можете использовать оператор new в DATABASEструктуре. Неиспользование означает, что строки Listне выделяются до первого GetString()вызова.


3

Здесь много хороших ответов, но в моем случае не удалось найти то, что я хотел, из «перечисления строк», которое было:

  1. Используется в операторе switch, например switch (myEnum)
  2. Может использоваться в параметрах функции, например, foo (тип myEnum)
  3. Может быть ссылка, например, myEnum.FirstElement
  4. Я могу использовать строки, например, foo ("FirstElement") == foo (myEnum.FirstElement)

1,2 и 4 могут быть решены с помощью C # Typedef строки (поскольку строки можно переключать в c #)

3 может быть решена с помощью статических константных строк. Так что, если у вас есть те же потребности, это самый простой подход:

public sealed class Types
{

    private readonly String name;

    private Types(String name)
    {
        this.name = name;

    }

    public override String ToString()
    {
        return name;
    }

    public static implicit operator Types(string str)
    {
        return new Types(str);

    }
    public static implicit operator string(Types str)
    {
        return str.ToString();
    }


    #region enum

    public const string DataType = "Data";
    public const string ImageType = "Image";
    public const string Folder = "Folder";
    #endregion

}

Это позволяет, например:

    public TypeArgs(Types SelectedType)
    {
        Types SelectedType = SelectedType
    }

а также

public TypeObject CreateType(Types type)
    {
        switch (type)
        {

            case Types.ImageType:
              //
                break;

            case Types.DataType:
             //
                break;

        }
    }

Где CreateType может быть вызван со строкой или типом. Однако недостатком является то, что любая строка автоматически является допустимым перечислением , это можно изменить, но тогда это потребует какой-то функции инициализации ... или, возможно, сделать их явным приведением внутреннего?

Теперь, если значение int было для вас важно (возможно, для скорости сравнения), вы можете использовать некоторые идеи из фантастического ответа Якуба Штурца и сделать что-то немного сумасшедшее, это мой удар в этом:

    public sealed class Types
{
    private static readonly Dictionary<string, Types> strInstance = new Dictionary<string, Types>();
    private static readonly Dictionary<int, Types> intInstance = new Dictionary<int, Types>();

    private readonly String name;
    private static int layerTypeCount = 0;
    private int value;
    private Types(String name)
    {
        this.name = name;
        value = layerTypeCount++;
        strInstance[name] = this;
        intInstance[value] = this;
    }

    public override String ToString()
    {
        return name;
    }


    public static implicit operator Types(int val)
    {
        Types result;
        if (intInstance.TryGetValue(val, out result))
            return result;
        else
            throw new InvalidCastException();
    }

    public static implicit operator Types(string str)
    {
        Types result;
        if (strInstance.TryGetValue(str, out result))
        {
            return result;
        }
        else
        {
            result = new Types(str);
            return result;
        }

    }
    public static implicit operator string(Types str)
    {
        return str.ToString();
    }

    public static bool operator ==(Types a, Types b)
    {
        return a.value == b.value;
    }
    public static bool operator !=(Types a, Types b)
    {
        return a.value != b.value;
    }

    #region enum

    public const string DataType = "Data";
    public const string ImageType = "Image";

    #endregion

}

но, конечно, "Типы Боб = 4;" было бы бессмысленно, если бы вы сначала не инициализировали их, что бы как-то победило ...

Но в теории TypeA == TypeB будет быстрее ...


3

Если я вас правильно понимаю, вы можете просто использовать .ToString () для получения имени перечисления из значения (при условии, что оно уже приведено как Enum); Если у вас был голый int (скажем, из базы данных или чего-то еще), вы можете сначала привести его к перечислению. Оба метода ниже приведут вас к имени enum.

AuthenticationMethod myCurrentSetting = AuthenticationMethod.FORMS;
Console.WriteLine(myCurrentSetting); // Prints: FORMS
string name = Enum.GetNames(typeof(AuthenticationMethod))[(int)myCurrentSetting-1];
Console.WriteLine(name); // Prints: FORMS

Имейте в виду, однако, что во втором методе предполагается, что вы используете целые числа, и ваш индекс основан на 1 (а не на 0). Функция GetNames также довольно тяжелая для сравнения: вы генерируете целый массив каждый раз, когда он вызывается. Как вы можете видеть в первом методе, .ToString () фактически вызывается неявно. Обе они уже упоминались в ответах, конечно, я просто пытаюсь уточнить различия между ними.


3

старый пост, но ...

Ответ на этот вопрос может быть очень простым. Используйте Enum.ToString () функция

Существует 6 перегрузок этой функции, вы можете использовать Enum.Tostring ("F") или Enum.ToString (), чтобы вернуть строковое значение. Не нужно беспокоиться ни о чем другом. Вот рабочая демка

Обратите внимание, что это решение может работать не для всех компиляторов ( эта демонстрация не работает должным образом ), но, по крайней мере, оно работает для последнего компилятора.


2

на основе MSDN: http://msdn.microsoft.com/en-us/library/cc138362.aspx

foreach (string str in Enum.GetNames(typeof(enumHeaderField)))
{
    Debug.WriteLine(str);
}

str будет именами полей


2
это даст имя перечисления, для этого вы также можете использовать ToString (), это не то, что было задано. выписка msdn.microsoft.com/en-us/library/system.enum.getname.aspx для получения дополнительной информации о вашем Бубе
Микки Perlstein

2

Что ж, после прочтения всего вышесказанного я чувствую, что ребята слишком усложнили вопрос преобразования счетчиков в строки. Мне понравилась идея иметь атрибуты над перечисляемыми полями, но я думаю, что атрибуты в основном используются для метаданных, но в вашем случае я думаю, что все, что вам нужно, это какая-то локализация.

public enum Color 
{ Red = 1, Green = 2, Blue = 3}


public static EnumUtils 
{
   public static string GetEnumResourceString(object enumValue)
    {
        Type enumType = enumValue.GetType();
        string value = Enum.GetName(enumValue.GetType(), enumValue);
        string resourceKey = String.Format("{0}_{1}", enumType.Name, value);
        string result = Resources.Enums.ResourceManager.GetString(resourceKey);
        if (string.IsNullOrEmpty(result))
        {
            result = String.Format("{0}", value);
        }
        return result;
    }
}

Теперь, если мы попытаемся вызвать вышеуказанный метод, мы можем вызвать его таким образом

public void Foo()
{
  var col = Color.Red;
  Console.WriteLine (EnumUtils.GetEnumResourceString (col));
}

Все, что вам нужно сделать, это просто создать файл ресурсов, содержащий все значения перечислителя и соответствующие строки

Имя ресурса Значение ресурса
Color_Red My String Color в красном
Color_Blue Blueeey
Color_Green Hulk Color

Что на самом деле очень приятно, так это то, что будет очень полезно, если вам нужно локализовать ваше приложение, поскольку все, что вам нужно сделать, это просто создать еще один файл ресурсов с вашим новым языком! и во-ля!


1

Когда я нахожусь в такой ситуации, я предлагаю решение ниже.

И как потребительский класс вы могли бы иметь

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyApp.Dictionaries
{
    class Greek
    {

        public static readonly string Alpha = "Alpha";
        public static readonly string Beta = "Beta";
        public static readonly string Gamma = "Gamma";
        public static readonly string Delta = "Delta";


        private static readonly BiDictionary<int, string> Dictionary = new BiDictionary<int, string>();


        static Greek() {
            Dictionary.Add(1, Alpha);
            Dictionary.Add(2, Beta);
            Dictionary.Add(3, Gamma);
            Dictionary.Add(4, Delta);
        }

        public static string getById(int id){
            return Dictionary.GetByFirst(id);
        }

        public static int getByValue(string value)
        {
            return Dictionary.GetBySecond(value);
        }

    }
}

И используя двунаправленный словарь: на основе этого ( https://stackoverflow.com/a/255638/986160 ), предполагая, что ключи будут связаны с отдельными значениями в словаре и аналогично ( https://stackoverflow.com/a / 255630/986160 ) но чуть изящнее. Этот словарь также перечисляем, и вы можете переходить от целых к строкам. Также вам не нужно иметь какую-либо строку в вашей кодовой базе, за исключением этого класса.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace MyApp.Dictionaries
{

    class BiDictionary<TFirst, TSecond> : IEnumerable
    {
        IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
        IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();

        public void Add(TFirst first, TSecond second)
        {
            firstToSecond.Add(first, second);
            secondToFirst.Add(second, first);
        }

        public TSecond this[TFirst first]
        {
            get { return GetByFirst(first); }
        }

        public TFirst this[TSecond second]
        {
            get { return GetBySecond(second); }
        }

        public TSecond GetByFirst(TFirst first)
        {
            return firstToSecond[first];
        }

        public TFirst GetBySecond(TSecond second)
        {
            return secondToFirst[second];
        }

        public IEnumerator GetEnumerator()
        {
            return GetFirstEnumerator();
        }

        public IEnumerator GetFirstEnumerator()
        {
            return firstToSecond.GetEnumerator();
        }

        public IEnumerator GetSecondEnumerator()
        {
            return secondToFirst.GetEnumerator();
        }
    }
}

1

Для больших наборов перечислений строк перечисленные примеры могут стать утомительными. Если вам нужен список кодов состояния или список других перечислений на основе строк, система атрибутов раздражает в использовании, а статический класс с экземплярами самого себя раздражает в настройке. Для моего собственного решения я использую шаблоны T4, чтобы упростить создание перечислений на основе строк. Результат получается аналогичным тому, как работает класс HttpMethod.

Вы можете использовать это так:

    string statusCode = ResponseStatusCode.SUCCESS; // Automatically converts to string when needed
    ResponseStatusCode codeByValueOf = ResponseStatusCode.ValueOf(statusCode); // Returns null if not found

    // Implements TypeConverter so you can use it with string conversion methods.
    var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(ResponseStatusCode));
    ResponseStatusCode code = (ResponseStatusCode) converter.ConvertFromInvariantString(statusCode);

    // You can get a full list of the values
    bool canIterateOverValues = ResponseStatusCode.Values.Any(); 

    // Comparisons are by value of the "Name" property. Not by memory pointer location.
    bool implementsByValueEqualsEqualsOperator = "SUCCESS" == ResponseStatusCode.SUCCESS; 

Вы начинаете с файла Enum.tt.

<#@ include file="StringEnum.ttinclude" #>


<#+
public static class Configuration
{
    public static readonly string Namespace = "YourName.Space";
    public static readonly string EnumName = "ResponseStatusCode";
    public static readonly bool IncludeComments = true;

    public static readonly object Nodes = new
    {
        SUCCESS = "The response was successful.",
        NON_SUCCESS = "The request was not successful.",
        RESOURCE_IS_DISCONTINUED = "The resource requested has been discontinued and can no longer be accessed."
    };
}
#>

Затем вы добавляете в свой файл StringEnum.ttinclude.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#@ CleanupBehavior processor="T4VSHost" CleanupAfterProcessingtemplate="true" #>

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;

namespace <#= Configuration.Namespace #>
{
    /// <summary>
    /// TypeConverter implementations allow you to use features like string.ToNullable(T).
    /// </summary>
    public class <#= Configuration.EnumName #>TypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            var casted = value as string;

            if (casted != null)
            {
                var result = <#= Configuration.EnumName #>.ValueOf(casted);
                if (result != null)
                {
                    return result;
                }
            }

            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            var casted = value as <#= Configuration.EnumName #>;
            if (casted != null && destinationType == typeof(string))
            {
                return casted.ToString();
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

    [TypeConverter(typeof(<#= Configuration.EnumName #>TypeConverter))]
    public class <#= Configuration.EnumName #> : IEquatable<<#= Configuration.EnumName #>>
    {
//---------------------------------------------------------------------------------------------------
// V A L U E S _ L I S T
//---------------------------------------------------------------------------------------------------
<# Write(Helpers.PrintEnumProperties(Configuration.Nodes)); #>

        private static List<<#= Configuration.EnumName #>> _list { get; set; } = null;
        public static List<<#= Configuration.EnumName #>> ToList()
        {
            if (_list == null)
            {
                _list = typeof(<#= Configuration.EnumName #>).GetFields().Where(x => x.IsStatic && x.IsPublic && x.FieldType == typeof(<#= Configuration.EnumName #>))
                    .Select(x => x.GetValue(null)).OfType<<#= Configuration.EnumName #>>().ToList();
            }

            return _list;
        }

        public static List<<#= Configuration.EnumName #>> Values()
        {
            return ToList();
        }

        /// <summary>
        /// Returns the enum value based on the matching Name of the enum. Case-insensitive search.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static <#= Configuration.EnumName #> ValueOf(string key)
        {
            return ToList().FirstOrDefault(x => string.Compare(x.Name, key, true) == 0);
        }


//---------------------------------------------------------------------------------------------------
// I N S T A N C E _ D E F I N I T I O N
//---------------------------------------------------------------------------------------------------      
        public string Name { get; private set; }
        public string Description { get; private set; }
        public override string ToString() { return this.Name; }

        /// <summary>
        /// Implcitly converts to string.
        /// </summary>
        /// <param name="d"></param>
        public static implicit operator string(<#= Configuration.EnumName #> d)
        {
            return d.ToString();
        }

        /// <summary>
        /// Compares based on the == method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(<#= Configuration.EnumName #> a, <#= Configuration.EnumName #> b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Compares based on the .Equals method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(<#= Configuration.EnumName #> a, <#= Configuration.EnumName #> b)
        {
            return a?.ToString() == b?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public override bool Equals(object o)
        {
            return this.ToString() == o?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(<#= Configuration.EnumName #> other)
        {
            return this.ToString() == other?.ToString();
        }

        /// <summary>
        /// Compares based on the .Name property
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.Name.GetHashCode();
        }
    }
}

<#+

public static class Helpers
{
        public static string PrintEnumProperties(object nodes)
        {
            string o = "";
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props = nodesTp.GetProperties().OrderBy(p => p.Name).ToArray();

            for(int i = 0; i < props.Length; i++)
            {
                var prop = props[i];
                if (Configuration.IncludeComments)
                {
                    o += "\r\n\r\n";
                    o += "\r\n        ///<summary>";
                    o += "\r\n        /// "+Helpers.PrintPropertyValue(prop, Configuration.Nodes);
                    o += "\r\n        ///</summary>";
                }

                o += "\r\n        public static readonly "+Configuration.EnumName+" "+prop.Name+ " = new "+Configuration.EnumName+"(){ Name = \""+prop.Name+"\", Description = "+Helpers.PrintPropertyValue(prop, Configuration.Nodes)+ "};";
            }

            o += "\r\n\r\n";

            return o;
        }

        private static Dictionary<string, string> GetValuesMap()
        {
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props= nodesTp.GetProperties();
            var dic = new Dictionary<string,string>();
            for(int i = 0; i < props.Length; i++)
            {
                var prop = nodesTp.GetProperties()[i];
                dic[prop.Name] = prop.GetValue(Configuration.Nodes).ToString();
            }
            return dic;
        }

        public static string PrintMasterValuesMap(object nodes)
        {
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props= nodesTp.GetProperties();
            string o = "        private static readonly Dictionary<string, string> ValuesMap = new Dictionary<string, string>()\r\n        {";
            for(int i = 0; i < props.Length; i++)
            {
                var prop = nodesTp.GetProperties()[i];
                o += "\r\n            { \""+prop.Name+"\", "+(Helpers.PrintPropertyValue(prop,Configuration.Nodes)+" },");
            }
            o += ("\r\n        };\r\n");

            return o;
        }


        public static string PrintPropertyValue(PropertyInfo prop, object objInstance)
        {
            switch(prop.PropertyType.ToString()){
                case "System.Double":
                    return prop.GetValue(objInstance).ToString()+"D";
                case "System.Float":
                    return prop.GetValue(objInstance).ToString()+"F";
                case "System.Decimal":
                    return prop.GetValue(objInstance).ToString()+"M";
                case "System.Long":
                    return prop.GetValue(objInstance).ToString()+"L";
                case "System.Boolean":
                case "System.Int16":
                case "System.Int32":
                    return prop.GetValue(objInstance).ToString().ToLowerInvariant();
                case "System.String":
                    return "\""+prop.GetValue(objInstance)+"\"";
            }

            return prop.GetValue(objInstance).ToString();
        }

        public static string _ (int numSpaces)
        {
            string o = "";
            for(int i = 0; i < numSpaces; i++){
                o += " ";
            }

            return o;
        }
}
#>

Наконец, вы перекомпилируете свой файл Enum.tt, и результат будет выглядеть так:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Linq;
using System.Collections.Generic;

namespace YourName.Space
{
    public class ResponseStatusCode
    {
//---------------------------------------------------------------------------------------------------
// V A L U E S _ L I S T 
//---------------------------------------------------------------------------------------------------



        ///<summary>
        /// "The response was successful."
        ///</summary>
        public static readonly ResponseStatusCode SUCCESS = new ResponseStatusCode(){ Name = "SUCCESS", Description = "The response was successful."};


        ///<summary>
        /// "The request was not successful."
        ///</summary>
        public static readonly ResponseStatusCode NON_SUCCESS = new ResponseStatusCode(){ Name = "NON_SUCCESS", Description = "The request was not successful."};


        ///<summary>
        /// "The resource requested has been discontinued and can no longer be accessed."
        ///</summary>
        public static readonly ResponseStatusCode RESOURCE_IS_DISCONTINUED = new ResponseStatusCode(){ Name = "RESOURCE_IS_DISCONTINUED", Description = "The resource requested has been discontinued and can no longer be accessed."};


        private static List<ResponseStatusCode> _list { get; set; } = null;
        public static List<ResponseStatusCode> ToList()
        {
            if (_list == null)
            {
                _list = typeof(ResponseStatusCode).GetFields().Where(x => x.IsStatic && x.IsPublic && x.FieldType == typeof(ResponseStatusCode))
                    .Select(x => x.GetValue(null)).OfType<ResponseStatusCode>().ToList();
            }

            return _list;
        }

        public static List<ResponseStatusCode> Values()
        {
            return ToList();
        }

        /// <summary>
        /// Returns the enum value based on the matching Name of the enum. Case-insensitive search.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static ResponseStatusCode ValueOf(string key)
        {
            return ToList().FirstOrDefault(x => string.Compare(x.Name, key, true) == 0);
        }


//---------------------------------------------------------------------------------------------------
// I N S T A N C E _ D E F I N I T I O N 
//---------------------------------------------------------------------------------------------------       
        public string Name { get; set; }
        public string Description { get; set; }
        public override string ToString() { return this.Name; }

        /// <summary>
        /// Implcitly converts to string.
        /// </summary>
        /// <param name="d"></param>
        public static implicit operator string(ResponseStatusCode d)
        {
            return d.ToString();
        }

        /// <summary>
        /// Compares based on the == method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(ResponseStatusCode a, ResponseStatusCode b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Compares based on the .Equals method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(ResponseStatusCode a, ResponseStatusCode b)
        {
            return a?.ToString() == b?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public override bool Equals(object o)
        {
            return this.ToString() == o?.ToString();
        }

        /// <summary>
        /// Compares based on the .Name property
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.Name.GetHashCode();
        }
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.