Пов’язування перерахунків із рядками в C #


361

Я знаю, що наступне неможливо, оскільки тип перерахування повинен бути цілим

enum GroupTypes
{
    TheGroup = "OEM",
    TheOtherGroup = "CMB"
}

З моєї бази даних я отримую поле з незрозумілими кодами ( OEMі CMBі). Я хотів би перетворити це поле на enumщось зрозуміле або щось інше. Тому що, якщо ціль - читабельність, рішення має бути стислим.

Які ще є варіанти?


можливий дублікат Enum ToString
nawfal

12
Я не впевнений, чому більшість відповідей не просто використовують "const string", а замість цього вони роблять спеціальні класи.
CTS_AE

1
Можливо, ви не зможете використовувати рядки, але ви можете використовувати символи просто чудово. Це варіант, якщо ви можете використовувати однобуквені значення.
Т. Сар

1
По-справжньому плутаний, чому рішення, запропоноване CTS_AE, вище, навіть не знаходиться в трійці найкращих відповідей.
Sinjai

@Sinjai Явне групування пов’язаних значень переважатиме штраф за непомітну втрату продуктивності, особливо в API або компоненті для багаторазового використання.
особа27

Відповіді:


402

Мені подобається використовувати властивості в класі замість методів, оскільки вони виглядають більш переглядно.

Ось приклад для Logger:

public class LogCategory
{
    private LogCategory(string value) { Value = value; }

    public string Value { get; set; }

    public static LogCategory Trace   { get { return new LogCategory("Trace"); } }
    public static LogCategory Debug   { get { return new LogCategory("Debug"); } }
    public static LogCategory Info    { get { return new LogCategory("Info"); } }
    public static LogCategory Warning { get { return new LogCategory("Warning"); } }
    public static LogCategory Error   { get { return new LogCategory("Error"); } }
}

Передати в якості параметра безпечні для рядка значення :

public static void Write(string message, LogCategory logCategory)
{
    var log = new LogEntry { Message = message };
    Logger.Write(log, logCategory.Value);
}

Використання:

Logger.Write("This is almost like an enum.", LogCategory.Info);

4
Лише внизу я можу придумати, що це було б трохи повільніше, але це в більшості випадків було б нехтувати. І не було б такої ж поведінки в редакторі. EG: перемикання на це не автоматично заповнює справу для кожної можливості. Крім цих незначних моментів, я думаю, що це, мабуть, досить просте рішення.
Борис Калленс

3
І легко використовувати словник <LogCategory, Action / Func> як комутатор. :)
Арніс Лапса

4
@ArnisL. Для роботи в якості ключа недостатньо, вам потрібно перекрити рівності () і GetHashCode (), і ви хочете зробити налаштування властивості Value приватним. Все-таки це не перерахунок.
Дейв Ван ден Ейнде

21
Для власного використання я розширив цю концепцію, змінивши ToStringметод повернення Value. А потім надаються оператори неявного лиття в рядок і з нього. public static implicit operator String(LogCategory category) { return Value; }.
Зарефет

6
Що з використанням цього в випадках переключення?
Девід

176

Ви також можете використовувати модель розширення:

public enum MyEnum
{
    [Description("String 1")]
    V1= 1,
    [Description("String 2")]
    V2= 2
} 

Ваш клас розширення

public static class MyEnumExtensions
{
    public static string ToDescriptionString(this MyEnum val)
    {
        DescriptionAttribute[] attributes = (DescriptionAttribute[])val
           .GetType()
           .GetField(val.ToString())
           .GetCustomAttributes(typeof(DescriptionAttribute), false);
        return attributes.Length > 0 ? attributes[0].Description : string.Empty;
    }
} 

використання:

MyEnum myLocal = MyEnum.V1;
print(myLocal.ToDescriptionString());

3
Дивіться також stackoverflow.com/questions/4367723/… для іншого розширення та від рядка до перерахунку шляхом опису.
Дейв

14
Я не можу не думати, що відображення перерахунку кожного разу, коли ви хочете відобразити текст, звучить настільки болісно з точки зору продуктивності!
Ліат

4
@Liath - ".ToString ()" вже використовує рефлексію, тому ти не дуже втрачаєш цей підхід і отримуєш читальність
Джеймс Кінг

1
Чи можете ви зробити Розширення загальним, щоб воно автоматично застосовувалося до всіх Enums?
еросебе

3
Щоб зробити загальним, використовуйте public static string ToDescriptionString(this Enum ...тобто без явного введення MyEnum.
LeeCambl

100

Як щодо використання статичного класу з константами?

static class GroupTypes
{
  public const string TheGroup = "OEM";
  public const string TheOtherGroup = "CMB";
}

void DoSomething(string groupType)
{
  if(groupType == GroupTypes.TheGroup)
  {
    // Be nice
  }  
  else if (groupType == GroupTypes.TheOtherGroup)
  {
    // Continue to be nice
  }
  else
  {
    // unexpected, throw exception?
  }
}

9
Домовились. У мене виникають проблеми, коли я бачу мету, що стоїть за складнішими рішеннями, за винятком, можливо, можливість переключитися на результат "перерахування".
підробка

@fakeleft ви не можете використовувати статичний тип класу із загальним типом (шаблоном), а може бути й іншими обмеженнями, я думаю, саме тому люди віддають перевагу "складнішим" рішенням.
eselk

2
Константи повинні бути внутрішніми або загальнодоступними, щоб це працювало
arviman

46
Статичні типи не можна використовувати як параметри.
Педро Морейра

2
Як вказує @PedroMoreira, ви не можете передавати GroupTypesяк тип аргументу, оскільки це статичний клас. Це проблема, яку вирішує відповідь навіть Міна. У цьому випадку вам натомість доведеться мати void DoSomething(string groupType), що означає, що groupTypeможе мати будь-яке значення рядка , навіть значення, яких ви не очікуєте, а це означає, що ви повинні бути готові до цих недійсних типів і вирішити, як з ними поводитися (наприклад, кинувши виняток). Навіть відповідь Мієна вирішує, що обмеживши кількість дійсних входів до параметрів, визначених LogCategoryкласом.
Фарап

30

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

Вам потрібно буде використовувати специфікатор "поля", щоб застосувати атрибути, наприклад:

enum GroupTypes
{
    [field:Description("OEM")]
    TheGroup,

    [field:Description("CMB")]
    TheOtherGroup
}

Потім ви б відобразили статичні поля типу enum (у цьому випадку GroupTypes) та отримали DescriptionAttributeзначення, яке шукали за допомогою відображення:

public static DescriptionAttribute GetEnumDescriptionAttribute<T>(
    this T value) where T : struct
{
    // The type of the enum, it will be reused.
    Type type = typeof(T);

    // If T is not an enum, get out.
    if (!type.IsEnum) 
        throw new InvalidOperationException(
            "The type parameter T must be an enum type.");

    // If the value isn't defined throw an exception.
    if (!Enum.IsDefined(type, value))
        throw new InvalidEnumArgumentException(
            "value", Convert.ToInt32(value), type);

    // Get the static field for the value.
    FieldInfo fi = type.GetField(value.ToString(), 
        BindingFlags.Static | BindingFlags.Public);

    // Get the description attribute, if there is one.
    return fi.GetCustomAttributes(typeof(DescriptionAttribute), true).
        Cast<DescriptionAttribute>().SingleOrDefault();
}

Я вирішив повернути DescriptionAttributeсебе вище, якщо ви хочете мати можливість визначити, чи застосовується атрибут навіть ні.


Хоча я запам'ятаю це для більш складних ситуацій, це досить складна ситуація із рівнем складності того, про що я заявив в ОП
Борис Калленс

26

Насправді це можна зробити дуже легко. Використовуйте наступний код.

enum GroupTypes
{
   OEM,
   CMB
};

Тоді, коли ви хочете отримати значення рядка кожного елемента enum, просто використовуйте наступний рядок коду.

String oemString = Enum.GetName(typeof(GroupTypes), GroupTypes.OEM);

Раніше я успішно використовував цей метод, а також використовував клас констант, щоб утримувати строкові константи, обидва працюють досить добре, але я, як правило, віддаю перевагу цьому.


Я думав те саме, але, мабуть, у цьому є якийсь улов ... Інакше я б підозрював, що більше людей запропонують це (можливо, я просто параноїк).
Маттайс Весселс

Єдиний замах, який я знаю про це, це те, що я вважаю, що він використовує рефлексію для з'ясування рядка. Як результат, якщо я тільки після рішення відстежувати постійну рядок, я зазвичай використовую клас для зберігання більшості моїх постійних рядків. Однак якщо у мене є ситуація, коли Enum - це правильне рішення (незалежно від отримання описового рядка про мої елементи Enum), то замість того, щоб зайва рядок плавала десь для управління, я просто використовую значення enum, як описано.
Артур C

+1 Це найкращий і найпростіший відповідь, а також має високі голоси тут , щоб довести це. Єдиний час, коли краще використовувати модель розширення, це коли вам потрібні пробіли в тексті (детальніше тут ).
SharpC

14
Ні, це лише отримання імені значення перерахунку, а не присвоєння рядка значенню enum. Мета OP - мати рядок, відмінний від значення enum, наприклад: TheGroup = "OEM", TheOtherGroup = "CMB".
Тім Атін

3
Я згоден з коментарем @ Тіма, це не те, що намагається робити ОП. Якщо вам цікаво, у чому полягає випадки використання цього питання, розгляньте ситуацію, коли пристрій приймає рядки як команди, але також має бути версія, що читається людиною ". Мені було потрібно в цьому пов’язати щось на кшталт "Оновити прошивку" з командою "UPDATEFW".
JYelton

20

Спробуйте додати константи до статичного класу. Ви не закінчуєте типом, але у вас будуть читані, організовані константи:

public static class GroupTypes {

    public const string TheGroup = "OEM";
    public const string TheOtherGroup = "CMB";

}

3
Складно перейти від коду назад до описового імені. Для пошуку відповідності вам доведеться використовувати роздуми над усіма полями const.
andleer

1
@andleer Я не розумію вашої турботи. Це рішення, яке я використовую.
VSO

Так, це насправді саме те, що я хотів. І це є найбільш стислим / елегантним рішенням, яке я бачу так само, як ніби я визначав перерахування w / int значень, але натомість зі значеннями рядків. 100% досконалий.
Чад

3
Проблема в тому, що він не працює як Enum в тому сенсі, що у нас не буде окремого типу з кінцевим списком значень. Функція, яка очікує їх, може використовуватися у рядках вільної форми, схильних до помилок.
Хуан Мартінес

14

Створіть другу перерахунок для вашої БД, що містить наступне:

enum DBGroupTypes
{
    OEM = 0,
    CMB = 1
}

Тепер ви можете використовувати Enum.Parse для отримання правильного значення DBGroupTypes з рядків "OEM" та "CMB". Потім ви можете перетворити їх в int та отримати правильні значення з правого перерахування, яке ви хочете використовувати далі у вашій моделі.


Це здається додатковим кроком у процесі, чому б не один клас, який обробляє все?
C. Ross

11
На відміну від використання атрибутів та рефлексії?
Дейв Ван ден Ейнде

13

Використовуйте клас.

Правка: кращий приклад

class StarshipType
{
    private string _Name;
    private static List<StarshipType> _StarshipTypes = new List<StarshipType>();

    public static readonly StarshipType Ultralight = new StarshipType("Ultralight");
    public static readonly StarshipType Light = new StarshipType("Light");
    public static readonly StarshipType Mediumweight = new StarshipType("Mediumweight");
    public static readonly StarshipType Heavy = new StarshipType("Heavy");
    public static readonly StarshipType Superheavy = new StarshipType("Superheavy");

    public string Name
    {
        get { return _Name; }
        private set { _Name = value; }
    }

    public static IList<StarshipType> StarshipTypes
    {
        get { return _StarshipTypes; }
    }

    private StarshipType(string name, int systemRatio)
    {
        Name = name;
        _StarshipTypes.Add(this);
    }

    public static StarshipType Parse(string toParse)
    {
        foreach (StarshipType s in StarshipTypes)
        {
            if (toParse == s.Name)
                return s;
        }
        throw new FormatException("Could not parse string.");
    }
}

1
Складно перейти від коду назад до описового імені. Для пошуку відповідності вам доведеться використовувати роздуми над усіма полями const.
andleer

1
Я бачу вашу думку. Я завантажу версію, яка акутно працює пізніше, але я визнаю, що вона досить важка.
К. Росс

Моя версія заснована на рішенні К. Росса stackoverflow.com/a/48441114/3862615
Роман М

7

Інший спосіб вирішити проблему - це перерахунок і масив рядків, які будуть зіставляти значення enum зі списком рядків:

public enum GroupTypes
{
    TheGroup  = 0,
    TheOtherGroup 
}

string[] GroupTypesStr = {
    "OEM",
    "CMB"
};

ви можете використовувати це приблизно так:

Log.Write(GroupTypesStr[(int)GroupTypes.TheOtherGroup]);

Це підкаже CMB

ПРО:

  1. Простий і чистий код.
  2. Висока продуктивність (особливо порівняно з тими підходами, що використовують класи)

Мінуси:

  1. Схильний зіпсувати список під час редагування, але це буде добре для короткого списку.

6

Ось метод розширення, який я використав для отримання значення enum у вигляді рядка. Спочатку тут - перелік.

public enum DatabaseEnvironment
{
    [Description("AzamSharpBlogDevDatabase")]
    Development = 1, 
    [Description("AzamSharpBlogQADatabase")]
    QualityAssurance = 2, 
    [Description("AzamSharpBlogTestDatabase")] 
    Test = 3
}

Атрибут Опис прийшов від System.ComponentModel.

І ось мій метод розширення:

public static string GetValueAsString(this DatabaseEnvironment environment) 
{
    // get the field 
    var field = environment.GetType().GetField(environment.ToString());
    var customAttributes = field.GetCustomAttributes(typeof (DescriptionAttribute), false);

    if(customAttributes.Length > 0)
    {
        return (customAttributes[0] as DescriptionAttribute).Description;  
    }
    else
    {
        return environment.ToString(); 
    }
}

Тепер ви можете отримати доступ до enum як значення рядка, використовуючи наступний код:

[TestFixture]
public class when_getting_value_of_enum
{
    [Test]
    public void should_get_the_value_as_string()
    {
        Assert.AreEqual("AzamSharpBlogTestDatabase",DatabaseEnvironment.Test.GetValueAsString());  
    }
}

5

Чи розглядали ви таблицю пошуку за допомогою словника?

enum GroupTypes
{
    TheGroup,
    TheOtherGroup
}

Dictionary<string, GroupTypes> GroupTypeLookup = new Dictionary<string, GroupTypes>();
// initialize lookup table:
GroupTypeLookup.Add("OEM", TheGroup);
GroupTypeLookup.Add("CMB", TheOtherGroup);

Потім ви можете використовувати GroupTypeLookup.TryGetValue () для пошуку рядка під час його читання.


Як легко отримати ключ для заданого значення?
eglasius

Питання не просило йти іншим шляхом. Але було б досить просто побудувати ще один словник, який іде іншим шляхом. Тобто словник <GroupTypes, string>.
Джим Мішель

4
public class DataType
{
    private readonly string value;
    private static readonly Dictionary<string, DataType> predefinedValues;

    public static readonly DataType Json = new DataType("json");
    public static readonly DataType Xml = new DataType("xml");
    public static readonly DataType Text = new DataType("text");
    public static readonly DataType Html = new DataType("html");
    public static readonly DataType Binary = new DataType("binary");

    static DataType()
    {
        predefinedValues = new Dictionary<string, DataType>();
        predefinedValues.Add(Json.Value, Json);
        predefinedValues.Add(Xml.Value, Xml);
        predefinedValues.Add(Text.Value, Text);
        predefinedValues.Add(Html.Value, Html);
        predefinedValues.Add(Binary.Value, Binary);
    }

    private DataType(string value)
    {
        this.value = value;
    }

    public static DataType Parse(string value)
    {
        var exception = new FormatException($"Invalid value for type {nameof(DataType)}");
        if (string.IsNullOrEmpty(value))
            throw exception;

        string key = value.ToLower();
        if (!predefinedValues.ContainsKey(key))
            throw exception;

        return predefinedValues[key];
    }

    public string Value
    {
        get { return value; }
    }
}

3

C # не підтримує перелічені рядки, але для більшості ситуацій ви можете використовувати Список або Словник, щоб отримати бажаний ефект.

Наприклад, для друку результатів пропуску / помилки:

List<string> PassFail = new List<string> { "FAIL", "PASS" };
bool result = true;
Console.WriteLine("Test1: " + PassFail[result.GetHashCode()]);

2

Я зробив би це у класі взагалі уникнути перерахунків. А потім за допомогою машинопису ви зможете створити об'єкт, коли захопите його з db.

IE:

public class Group
{
    public string Value{ get; set; }
    public Group( string value ){ Value = value; } 
    public static Group TheGroup() { return new Group("OEM"); }
    public static Group OtherGroup() { return new Group("CMB"); }

}

2

Я просто створив би словник і використав код як ключ.

Редагувати: Щоб звернутися до коментаря про пошук зворотного пошуку (пошук ключа), це не було б дуже ефективно. Якщо це необхідно, я б написав новий клас, щоб обробити це.


Ви також можете легко взяти ключ для заданого значення?
eglasius

Для C.Ross - я не впевнений, що ти маєш на увазі. Ви можете прочитати значення з db та динамічно заповнити словник.
jhale

2

Перше моє запитання - чи маєте ви доступ до самої бази даних? Це слід нормалізувати в базі даних, в ідеалі, інакше будь-яке рішення буде схильне до помилок. З мого досвіду, поля даних, наповнені "OEM" та "CMB", як правило, закінчуються тим, що такі речі, як "oem" та інші "дані про лайно", змішуються з часом .... Якщо ви можете нормалізувати їх, ви можете використовувати ключ в таблиці, що містить елементи, як ваш Enum, і ви закінчили, зі значно більш чистою структурою.

Якщо цього немає в наявності, я б зробив ваш Enum і склав клас для розбору вашої струни в Enum для вас. Це, принаймні, надасть вам деяку гнучкість у застосуванні нестандартних записів та набагато більшу гнучкість для вилучення помилок або помилок обробки, ніж будь-яке обхідне рішення за допомогою Enum.Parse / Reflection / тощо. Словник працював би, але може вийти з ладу, якщо у вас виникнуть проблеми зі справами тощо.

Я рекомендую написати клас, щоб ви могли:

// I renamed this to GroupType, since it sounds like each element has a single type...
GroupType theType = GroupTypeParser.GetGroupType(theDBString);

Це зберігає більшу частину вашої читабельності, не змінюючи БД.


2

Якщо я правильно розумію, вам потрібно перетворення від рядка до перерахунку:

enum GroupTypes {
    Unknown = 0,
    OEM = 1,
    CMB = 2
}
static GroupTypes StrToEnum(string str){
    GroupTypes g = GroupTypes.Unknown;
    try {
        object o = Enum.Parse(typeof(GroupTypes), str, true);
        g = (GroupTypes)(o ?? 0);
    } catch {
    }
    return g;
}
// then use it like this
GroupTypes g1 = StrToEnum("OEM");
GroupTypes g2 = StrToEnum("bad value");

Якщо ви хочете, ви можете зробити його більш привабливим для загальних даних для типу enum.


2

У VS 2015 ви можете використовувати nameof

public class LogCategory
{
    public static string Trace;
    public static string Debug;
    public static string Info;
    public static string Warning;
    public static string Error;
}

Використання:

Logger.Write("This is almost like an enum.", nameof(LogCategory.Info));

2

Невеликий підхід до методу розширення Glennular, тому ви можете використовувати розширення для інших речей, ніж лише ENUM;

using System;
using System.ComponentModel;
namespace Extensions {
    public static class T_Extensions {
        /// <summary>
        /// Gets the Description Attribute Value
        /// </summary>
        /// <typeparam name="T">Entity Type</typeparam>
        /// <param name="val">Variable</param>
        /// <returns>The value of the Description Attribute or an Empty String</returns>
        public static string Description<T>(this T t) {
            DescriptionAttribute[] attributes = (DescriptionAttribute[])t.GetType().GetField(t.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
            return attributes.Length > 0 ? attributes[0].Description : string.Empty;
        }
    }
}

Або за допомогою Linq

using System;
using System.ComponentModel;
using System.Linq;

namespace Extensions {


public static class T_Extensions {
        public static string Description<T>(this T t) =>
            ((DescriptionAttribute[])t
            ?.GetType()
            ?.GetField(t?.ToString())
            ?.GetCustomAttributes(typeof(DescriptionAttribute), false))
            ?.Select(a => a?.Description)
            ?.FirstOrDefault() 
            ?? string.Empty;  
    }
}

2

Після відповіді @Even Mien я спробував піти трохи далі і зробити це загальним, я, здається, майже є, але один випадок все ще противиться, і я, мабуть, можу трохи спростити свій код.
Я розміщую його тут, якщо хтось бачить, як я міг би вдосконалитись і особливо змусити його працювати, оскільки я не можу призначити його з рядка

Поки що у мене є такі результати:

        Console.WriteLine(TestEnum.Test1);//displays "TEST1"

        bool test = "TEST1" == TestEnum.Test1; //true

        var test2 = TestEnum.Test1; //is TestEnum and has value

        string test3 = TestEnum.Test1; //test3 = "TEST1"

        var test4 = TestEnum.Test1 == TestEnum.Test2; //false
         EnumType<TestEnum> test5 = "TEST1"; //works fine

        //TestEnum test5 = "string"; DOESN'T compile .... :(:(

Де відбувається магія:

public abstract  class EnumType<T>  where T : EnumType<T>   
{

    public  string Value { get; set; }

    protected EnumType(string value)
    {
        Value = value;
    }


    public static implicit operator EnumType<T>(string s)
    {
        if (All.Any(dt => dt.Value == s))
        {
            Type t = typeof(T);

            ConstructorInfo ci = t.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,null, new Type[] { typeof(string) }, null);

            return (T)ci.Invoke(new object[] {s});
        }
        else
        {
            return null;
        }
    }

    public static implicit operator string(EnumType<T> dt)
    {
        return dt?.Value;
    }


    public static bool operator ==(EnumType<T> ct1, EnumType<T> ct2)
    {
        return (string)ct1 == (string)ct2;
    }

    public static bool operator !=(EnumType<T> ct1, EnumType<T> ct2)
    {
        return !(ct1 == ct2);
    }


    public override bool Equals(object obj)
    {
        try
        {
            return (string)obj == Value;
        }
        catch
        {
            return false;
        }
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }

    public static IEnumerable<T> All
     => typeof(T).GetProperties()
       .Where(p => p.PropertyType == typeof(T))
       .Select(x => (T)x.GetValue(null, null));



}

Я лише тоді мушу заявити про це для своїх перерахунків:

public class TestEnum : EnumType<TestEnum> 
{

    private TestEnum(string value) : base(value)
    {}

    public static TestEnum Test1 { get { return new TestEnum("TEST1"); } }
    public static TestEnum Test2 { get { return new TestEnum("TEST2"); } }
}

Дякую за цю чудову роботу, я довго шукав такий підхід. Я думаю, ви повинні отримати 1000 балів за це
user3492977

О, дякую за цей коментар, і дякую, що нагадали мені про це, я два роки не використовував c #, коли писав цей шматочок коду, я мушу незабаром повернутися до нього!
Ломітрані

@ User3492977 я , нарешті , повернувся до нього і зробив його повністю Функціональне, я до сих пір сумнівно , хоча , якщо це відмінна ідея , або марна річ: D stackoverflow.com/questions/62043138 / ...
Lomithrani

2

Нове в .Net Core 3.0 / C # 8.0 (якщо ваше робоче середовище дозволяє оновити проект) - це короткий вимикач, який виглядає дещо важко. Зрештою, це та ж стара нудна заява про перемикання, яку ми використовуємо роками.

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

public static RGBColor FromRainbow(Rainbow colorBand) =>
colorBand switch
{
    Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
    Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
    Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
    Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
    Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
    Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
    Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
    _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
};

Ви помітите, що код, над яким я скопіював звідси , насправді використовує enum як парам.

Це не зовсім те, що ви хочете (і повірте, я хотів щось подібне до того, що ОП просить давно), але я насправді відчуваю, що це дещо з оливкової гілки від MS. JMO.

Сподіваюся, це комусь допоможе!


2

Я використовував структуру, про яку згадували у попередній відповіді, але усунув будь-яку складність. Для мене це найбільше схоже на створення перерахування рядків. Він використовується таким же чином, як і перерахування.

    struct ViewTypes
    {
        public const string View1 = "Whatever string you like";
        public const string View2 = "another string";
    }

Приклад використання:

   switch( some_string_variable )
   {
      case ViewTypes.View1: /* do something */ break;
      case ViewTypes.View2: /* do something else */ break;
   }

1

Я навіть реалізував кілька переліків, як це запропонував @Even (через class Xта public static Xучасників), просто щоб потім дізнатися, що в ці дні, починаючи з .Net 4.5, є право ToString() метод.

Тепер я все перетворюю на переліки.


1

Це спосіб використовувати його як сильно набраний параметр або як рядок :

public class ClassLikeEnum
{
    public string Value
    {
        get;
        private set;
    }

    ClassLikeEnum(string value) 
    {
        Value = value;
    }

    public static implicit operator string(ClassLikeEnum c)
    {
        return c.Value;
    }

    public static readonly ClassLikeEnum C1 = new ClassLikeEnum("RandomString1");
    public static readonly ClassLikeEnum C2 = new ClassLikeEnum("RandomString2");
}

1

Можна використовувати два перерахунки. Один для бази даних, а другий для читабельності.

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

// keep in sync with GroupTypes
public enum GroupTypeCodes
{
    OEM,
    CMB
}

// keep in sync with GroupTypesCodes
public enum GroupTypes
{
    TheGroup = GroupTypeCodes.OEM,
    TheOtherGroup = GroupTypeCodes.CMB
}

Щоб скористатися нею, ви просто спочатку перетворите в код:

GroupTypes myGroupType = GroupTypes.TheGroup;
string valueToSaveIntoDatabase = ((GroupTypeCodes)myGroupType).ToString();

Тоді, якщо ви хочете зробити це ще зручніше, ви можете додати функцію розширення, яка працює лише для цього типу enum:

public static string ToString(this GroupTypes source)
{
    return ((GroupTypeCodes)source).ToString();
}

і ви можете просто зробити:

GroupTypes myGroupType = GroupTypes.TheGroup;
string valueToSaveIntoDatabase = myGroupType.ToString();

Це погана практика: якщо залежна enumвід наміченої зміни значення одна може ненавмисно зіпсувати іншу.
Лоренц Ло Зауер

1

Я в основному шукав відповідь на роздуми від @ArthurC

Щоб трохи розширити його відповідь, ви можете зробити її ще кращою, маючи загальну функцію:

    // If you want for a specific Enum
    private static string EnumStringValue(GroupTypes e)
    {
        return EnumStringValue<GroupTypes>(e);
    }

    // Generic
    private static string EnumStringValue<T>(T enumInstance)
    {
        return Enum.GetName(typeof(T), enumInstance);
    } 

Тоді ви можете просто обернути все, що у вас є

EnumStringValue(GroupTypes.TheGroup) // if you incorporate the top part

або

EnumStringValue<GroupTypes>(GroupTypes.TheGroup) // if you just use the generic

1

Взято від @EvenMien та додано в деяких коментарях. (Також для мого власного випадку використання)

public struct AgentAction
{
    private AgentAction(string value) { Value = value; }

    public string Value { get; private set; }

    public override string ToString()
    {
        return this.Value;
    }

    public static AgentAction Login = new AgentAction("Logout");
    public static AgentAction Logout = new AgentAction("Logout");

    public static implicit operator string(AgentAction action) { return action.ToString(); }
}

1

Додавання цього класу

public class DatabasePreference {
    public DatabasePreference([CallerMemberName] string preferenceName = "") {
        PreferenceName = preferenceName;
    }
    public string PreferenceName;
}

Ця робота використовується CallerMemberName для мінімізації кодування

Використання:

//Declare names
public static DatabasePreference ScannerDefaultFlashLight = new DatabasePreference();
public static DatabasePreference ScannerQrCodes = new DatabasePreference();
public static DatabasePreference Scanner1dCodes = new DatabasePreference();

Перевірте:

Console.WriteLine(ScannerDefaultFlashLight.PreferenceName);
Console.WriteLine(ScannerDefaultFlashLight.Scanner1dCodes);

вихід:

ScannerDefaultFlashLight
Scanner1dCodes

0

Виходячи з інших думок, це те, що я придумав. Цей підхід дозволяє уникнути необхідності вводити .Value там, де ви хочете отримати постійне значення.

У мене базовий клас для всіх рядкових перерахунків на зразок цього:

using System;
using Newtonsoft.Json;

[JsonConverter(typeof(ConstantConverter))]
public class StringEnum: IConvertible
{
    public string Value { get; set; }

    protected StringEnum(string value)
    {
        Value = value;
    }

    public static implicit operator string(StringEnum c)
    {
        return c.Value;
    }
    public string ToString(IFormatProvider provider)
    {
        return Value;
    }

    public TypeCode GetTypeCode()
    {
        throw new NotImplementedException();
    }

    public bool ToBoolean(IFormatProvider provider)
    {
        throw new NotImplementedException();
    }
    //The same for all the rest of IConvertible methods
}

JsonConverter такий:

using System;
using Newtonsoft.Json;

class ConstantConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null)
        {
            serializer.Serialize(writer, null);
        }
        else
        {
            serializer.Serialize(writer, value.ToString());
        }
    }
}

І власне перерахунок рядків буде приблизно таким:

public sealed class Colors : StringEnum
{
    public static Colors Red { get { return new Catalog("Red"); } }
    public static Colors Yellow { get { return new Catalog("Yellow"); } }
    public static Colors White { get { return new Catalog("White"); } }

    private Colors(string value) : base(value) { }
}

І з цим ви можете просто використовувати Color.Red, щоб навіть серіалізувати до json, не використовуючи властивість Value


0

Мені не потрібно було нічого надійного, як зберігання рядка в атрибутах. Мені просто потрібно було перетворити щось на кшталт MyEnum.BillEveryWeek"законопроект щотижня" або MyEnum.UseLegacySystem"використовувати застарілу систему" - в основному розбити перелік за допомогою його верблюда на окремі малі слова.

public static string UnCamelCase(this Enum input, string delimiter = " ", bool preserveCasing = false)
{
    var characters = input.ToString().Select((x, i) =>
    {

       if (i > 0 && char.IsUpper(x))
       {
           return delimiter + x.ToString(CultureInfo.InvariantCulture);
       }
       return x.ToString(CultureInfo.InvariantCulture);

    });

    var result = preserveCasing
       ? string.Concat(characters)
       : string.Concat(characters).ToLower();

    var lastComma = result.LastIndexOf(", ", StringComparison.Ordinal);

    if (lastComma > -1)
    {
       result = result.Remove(lastComma, 2).Insert(lastComma, " and ");
    }

    return result;
}

MyEnum.UseLegacySystem.UnCamelCase() виводи "використовувати застарілу систему"

Якщо у вас встановлено кілька прапорів, це перетворить це на звичайну англійську (розміщено комами, крім "і" замість останньої коми).

var myCustomerBehaviour = MyEnum.BillEveryWeek | MyEnum.UseLegacySystem | MyEnum.ChargeTaxes;

Console.WriteLine(myCustomerBehaviour.UnCamelCase());
//outputs "bill every week, use legacy system and charge taxes"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.