Рядкове зображення Enum


912

У мене таке перерахування:

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

Однак проблема полягає в тому, що мені потрібно слово "ФОРМИ", коли я запитую 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
Приємно! Я можу використовувати це для перекладу значень перерахунків до локалізованих рядків.
Øyvind Skaar

5
Незважаючи на те, що ви можете виявити цього довго звитого, це насправді досить гнучкий спосіб піти на інші речі. Як зазначав один з моїх колег, це може бути використано у багатьох випадках для заміни Enum Helpers, які відображають коди баз даних для перерахування значень тощо ...
BenAlabaster

27
Класи атрибутів суфіксів MSDN рекомандів із суфіксом "Атрибут". Тож "клас StringValueAttribute";)
serhio

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

5
Цей підхід використовує рефлексію для читання значень атрибутів, і це дуже повільно, якщо вам доведеться дзвонити GetStringValue () багато разів за моїм досвідом. Шаблон типу безпечно-перерахунку швидше.
Rn222

Відповіді:


868

Спробуйте тип-безпечно-переписуючий шаблон.

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 member" не кидала NullReferenceException при виклику конструктора екземпляра, обов'язково поставте поле словника перед полями "enum member" у вашому класі. Це відбувається тому, що ініціалізатори статичного поля викликаються в порядку декларування та перед статичним конструктором, створюючи дивну та необхідну, але заплутану ситуацію, що конструктор екземпляра може бути викликаний до ініціалізації всіх статичних полів і до виклику статичного конструктора.
  • заповнення цього відображення в конструкторі екземпляра

    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
Це схоже на перерахунок, але це не перерахунок. Я можу собі уявити, що це викликає цікаві проблеми, якщо люди почнуть намагатися порівнювати Методи аутентифікації. Можливо, вам теж потрібно перевантажувати різних операторів рівності.
Мураха

36
@Ant: Мені не доведеться. Оскільки у нас є лише один екземпляр кожного AuthenticationMethod, рівне значення посилання, успадковане від Object, працює чудово.
Якуб Штурч

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

21
@Jakub Дуже цікаво. Мені довелося пограти з ним, щоб зрозуміти, як ним користуватися, і усвідомити його переваги. Це загальнодоступний, нестатичний клас, але його неможливо створити, і ви можете отримати доступ лише до його статичних членів. В основному, він поводиться як енмуз. Але найкраще ... статичні члени набираються класу, а не загального рядка чи int. Це ... [чекайте] ... введіть безпечний перелік! Дякуємо, що допомогли мені зрозуміти.
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 відображає імена полів в enum - те саме, що і .ToString (). Якщо продуктивність - це проблема, це може бути проблемою. Я б не переживав про це, якщо ви не конвертуєте безліч переліків.
Кіт

8
Іншим варіантом, який слід врахувати, якщо вам потрібен перерахунок з додатковою функціональністю, - це "згорнути yr own", використовуючи структуру ... ви додаєте статичні властивості, що мають лише ім'я, щоб представити значення enum, які ініціалізуються на конструктори, що генерують окремі екземпляри структури ...
Чарльз Бретана

1
тоді ви можете додати будь-яких інших членів структури, щоб реалізувати будь-яку функціональність, яку ви хочете, щоб цей "перелік" мав ...
Чарльз Бретана

2
Проблема тут полягає в тому, що GetName не піддається локалізації. Це не завжди викликає занепокоєння, але варто пам’ятати про щось.
Joel Coehoorn

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, який нескінченно більш багаторазові. Звичайно, це не дозволяє вам мати різне дружнє ім'я , ніж ваше перерахування , яке ваше рішення дійсно забезпечує.

Мені подобається ваше оригінальне рішення, хоча і для складніших сценаріїв. Ви можете зробити своє рішення ще на крок і зробити ваш 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;
}

Тоді ви можете легко отримати доступ до нього прямо з вашої інстанції:

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

2
Це не допоможе, якщо "дружньому імені" потрібен пробіл. Такі як "Форми аутентифікації"
Ray Booysen

4
Тож переконайтесь, що enum названо з великими кришками типу FormsAuthentication та вставте пробіл перед будь-якими кришками, яких немає на початку. Це не ракетна наука, щоб вставити пробіл у рядок ...
BenAlabaster

4
Автоматичний інтервал між іменами корпусу Паскаля стає проблематичним, якщо вони містять абревіатури, які слід писати з великої літери, наприклад XML або GPS.
Річард Єв

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

Не елегантно робити ці маленькі «хаки», які вони є. Я розумію, що говорить ОП, і я намагаюся знайти подібне рішення, тобто використовуючи елегантність Enums, але маю можливість легко отримати доступ до пов’язаного повідомлення. Єдине рішення, про яке я можу придумати, - це застосувати якесь відображення між ім'ям enum та значенням рядка, але це не може вирішити питання щодо збереження рядкових даних (однак це робить практичним для сценаріїв, де потрібно мати кілька регіонів тощо) )
Тахір Халід

72

На жаль, рефлексія для отримання атрибутів на перерахунки відбувається досить повільно:

Дивіться це запитання: Хтось знає швидкий спосіб дістатися до спеціальних атрибутів із значенням enum?

На .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"на час компіляції , що робить його найшвидшим способом отримання імен enum.

Зауважте, що це перетворить явний перерахунок у вбудовану константу, тому він не працює для перерахунків, які у вас є в змінній. Тому:

nameof(AuthenticationMethod.FORMS) == "FORMS"

Але ...

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

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

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.
Рейф

7
Чому всі інші надто ускладнюють це?
Брент

18
@Brent Тому що найчастіше ви маєте .ToString()значення, яке відрізняється від зручного для вас значення.
Novitchi S

2
@Brent - адже це інше, ніж питання, яке задають. Питання, яке задають, - як отримати цей рядок зі змінної, якій було призначено перелічене значення. Це динамічно під час виконання. Це перевірка визначення типу та встановлення під час виконання.
Хоган

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

Я використовую атрибут Опис з простору імен System.ComponentModel. Просто прикрасьте enum та використовуйте цей код, щоб отримати його:

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

Мені дуже подобається відповідь Якуба Штурца, але недолік полягає в тому, що ти не можеш використовувати його із заявою про переключення. Ось трохи модифікована версія його відповіді, яку можна використовувати з оператором переключення:

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

Таким чином, ви отримуєте всі переваги відповіді Якуба Штурца, а також ми можемо використовувати його з таким записом:

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

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

Цікава ідея @kjhf. Хоча моє занепокоєння полягатиме в тому, що якщо хтось змінить код, то значення, присвоєне значенням enum, може також змінитися. Наприклад, це може призвести до отримання неправильного значення enum, коли значення enum зберігається у файл / базу даних, змінюється порядок рядків "новий AuthenticationMethod (...)" (наприклад, один видаляється), а потім запустити додаток ще раз і отримати значення перерахунку з файлу / бази даних; значення перерахунку може не відповідати методу аутентифікації, який був збережений спочатку.
смертельної собаки

Хороший момент - хоча я сподіваюся, що в цих конкретних випадках люди не будуть покладатися на ціле значення enum (або переупорядковувати код enum.) - і це значення використовується виключно як перемикач і, можливо, альтернатива .Equals () та GetHashCode (). Якщо це стосується, ви завжди можете розмістити величезний коментар із написом "НЕ ЧИТАТИ": стор
kjhf

Ви не можете просто перевантажувати =оператора, щоб дозволити комутатору працювати? Я робив це в VB і тепер можу ним користуватисяselect case заяві.
user1318499

@ user1318499 Ні, C # має більш жорсткі правила щодо оператора перемикання, ніж VB. Ви не можете використовувати екземпляри класу для оператора Case; можна використовувати лише постійні примітиви.
смертельної собаки

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

У своєму запитанні ви ніколи не говорили, що вам дійсно потрібне числове значення перерахунку де завгодно.

Якщо вам не потрібен і просто потрібен enum рядка типу (який не є інтегральним типом, тому не може бути базою enum), ось такий спосіб:

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

ви можете використовувати той самий синтаксис, що і enum для посилання на нього

if (bla == AuthenticationMethod.FORMS)

Це буде трохи повільніше, ніж із числовими значеннями (порівнюючи рядки замість чисел), але на плюс-стороні він не використовує відображення (повільний) для доступу до рядка.


якщо ви використовуєте "const" замість "static readonly", ви можете використовувати значення як мітки регістру в операторі перемикача.
Ред Н.

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
Bernie, 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", але значення якого є рядками замість ints, ось найпростіше рішення:

    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
consts не є корисним для загальнодоступних класів, оскільки вони випікаються під час компіляції, ви не можете замінити DLL третьої сторони, не перекомпілювавши весь код на consts.Зміщення продуктивності consts vs static readonly є незначним.
Крістіан Вільямс

7

Коли я стикаюся з цією проблемою, є кілька питань, на які я намагаюся спочатку знайти відповіді:

  • Чи є назви моїх значень перерахунків достатньо дружніми за цією метою, чи мені потрібно надати дружніші?
  • Чи потрібно мені в обидва кінці? Тобто, чи потрібно мені брати текстові значення та аналізувати їх на значення перерахунків?
  • Це щось, що мені потрібно зробити для багатьох переліків у моєму проекті, чи лише один?
  • Якими елементами інтерфейсу я буду представляти цю інформацію, зокрема, чи буду я обов'язковим до користувальницького інтерфейсу чи використовую аркуші властивостей?
  • Чи потрібно це локалізувати?

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

Загалом, якщо відповіді на вищезазначені запитання дозволяють припустити, що це не спрацює, наступним моїм кроком є ​​створення та заповнення статики Dictionary<MyEnum, string>чи, можливо, а Dictionary<Type, Dictionary<int, string>>. Я схильний пропускати проміжний крок deco-the-code-with-attributes, тому що зазвичай знижується щука наступним чином - це необхідність зміни дружніх значень після розгортання (часто, але не завжди, через локалізацію).


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
}

Яскравий!


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

Якщо ви думаєте про проблему, яку ми намагаємося вирішити, це взагалі не потрібний перелік. Нам потрібен об’єкт, який дозволяє визначити певну кількість значень з іншими; іншими словами, визначити клас.

Найкращий варіант, який я бачу тут, є безпечний тип перерахунку Якуба Штурца.

Подивись на це:

  • Він має приватний конструктор, тому лише клас може визначати дозволені значення.
  • Це герметичний клас, тому значення не можуть бути змінені шляхом успадкування.
  • Це безпечно для типів, що дозволяє вашим методам вимагати лише цього типу.
  • Немає відображення результатів відображення, отриманих при доступі до значень.
  • І нарешті, його можна модифікувати, щоб об'єднати більше двох полів разом, наприклад Ім'я, Опис та числове значення.

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 запропонує ім'я enum, якщо клас буде позначено коментарем xml <completitionlist>. (Працює як у C #, так і в VB)

Демонстрація Intellisense

  • Використання, подібне до звичайного перерахунку:
///<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() дзвінка.

Ви можете додатково використовувати нового оператора на DATABASEstruct. Якщо його не використовувати, рядки Listне виділяються до першого GetString()виклику.


3

Тут багато чудових відповідей, але в моєму випадку не вирішили те, що я хотів із "струнного переліку", а саме:

  1. Використовується в операторі комутатора, наприклад, перемикач (myEnum)
  2. Може використовуватися в параметрах функції, наприклад foo (тип myEnum)
  3. Можна посилатися, наприклад, на myEnum.FirstElement
  4. Я можу використовувати рядки, наприклад foo ("FirstElement") == foo (myEnum.FirstElement)

1,2 і 4 насправді можна вирішити за допомогою рядка C # Typedef (оскільки рядки перемикаються в c #)

3 можна вирішити статичними рядками const. Тож якщо у вас однакові потреби, це найпростіший підхід:

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 можна викликати за допомогою рядка або типу. Однак недоліком є ​​те, що будь-яка рядок автоматично є дійсним перерахунком , це може бути змінено, але тоді вона потребує якоїсь функції init ... або, можливо, зробити їх явним введенням внутрішнього?

Тепер, якщо значення 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

}

але звичайно "Типи bob = 4;" було б безглуздо, якщо б ви не ініціалізували їх спочатку, що б начебто перемогло точку ...

Але теоретично TypeA == TypeB був би швидшим ...


3

Якщо я правильно вас зрозумів, ви можете просто скористатися .ToString (), щоб отримати ім'я enum зі значення (якщо припустити, що воно вже передано як 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

Майте на увазі, однак, друга методика передбачає, що ви використовуєте ints, а ваш індекс - 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
це дасть ім'я enum, ви також можете використовувати ToString (), це не те, що запитували. checkout msdn.microsoft.com/en-us/library/system.enum.getname.aspx для отримання додаткової інформації про ваш bubu
Міккі Перлштайн

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_Blue Blueeey
Color_Green Халк Колір

Що насправді дуже приємно в тому, що це буде дуже корисно, якщо вам потрібно локалізувати вашу заявку, оскільки все, що вам потрібно зробити, це просто створити ще один файл ресурсу з новою мовою! і Во-ля!


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 ), але трохи елегантніше. Цей словник також безліч, і ви можете повертатися назад і назад від int до рядків. Крім того, вам не потрібно мати жодної рядки у вашій кодовій базі, за винятком цього класу.

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.