С# Получение значений Enum

У меня есть перечисление, содержащее следующее (например):

  • Королевство Соединенное,
  • UnitedStates,
  • Франция,
  • Португалия

В моем коде я использую Country.UnitedKingdom, но я хочу, чтобы значение было UK, если я присваиваю ему строку , например.

Возможно ли это?

Ответ 1

Вы не можете назначить значение перечисления для начала строки. Вам нужно будет вызвать ToString(), который преобразует Country.UnitedKingdom в "UnitedKingdom".

Возможны два варианта:

  • Создайте Dictionary<Country, string>
  • Оператор switch
  • Украсить каждое значение атрибутом и загрузить его с отражением

Комментарии о каждом из них...

Пример кода для Dictionary<Country,string>

using System;
using System.Collections.Generic;

enum Country
{
    UnitedKingdom, 
    UnitedStates,
    France,
    Portugal
}

class Test
{
    static readonly Dictionary<Country, string> CountryNames =
        new Dictionary<Country, string>
    {
        { Country.UnitedKingdom, "UK" },
        { Country.UnitedStates, "US" },
    };

    static string ConvertCountry(Country country) 
    {
        string name;
        return (CountryNames.TryGetValue(country, out name))
            ? name : country.ToString();
    }

    static void Main()
    {
        Console.WriteLine(ConvertCountry(Country.UnitedKingdom));
        Console.WriteLine(ConvertCountry(Country.UnitedStates));
        Console.WriteLine(ConvertCountry(Country.France));
    }
}

Возможно, вы захотите поместить логику ConvertCountry в метод расширения. Например:

// Put this in a non-nested static class
public static string ToBriefName(this Country country) 
{
    string name;
    return (CountryNames.TryGetValue(country, out name))
        ? name : country.ToString();
}

Тогда вы могли бы написать:

string x = Country.UnitedKingdom.ToBriefName();

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

Оператор switch

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

public static string ToBriefName(this Country country) 
{
    switch (country)
    {
        case Country.UnitedKingdom:  return "UK";
        case Country.UnitedStates:   return "US";
        default:                     return country.ToString();
    }
}

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

DescriptionAttribute

Точка Rado made о том, что код для DescriptionAttribute является многоразовым, является хорошим, но в этом случае я бы рекомендовал против использования отражения каждый раз, когда вам нужно чтобы получить значение. Я бы, вероятно, написал общий статический класс для хранения таблицы поиска (возможно, Dictionary, возможно, с помощью пользовательского сопоставления, как указано в комментариях). Методы расширения не могут быть определены в родовых классах, поэтому вы, вероятно, получите что-то вроде:

public static class EnumExtensions
{
    public static string ToDescription<T>(this T value) where T : struct
    {
        return DescriptionLookup<T>.GetDescription(value);
    }

    private static class DescriptionLookup<T> where T : struct
    {
        static readonly Dictionary<T, string> Descriptions;

        static DescriptionLookup()
        {
            // Initialize Descriptions here, and probably check
            // that T is an enum
        }

        internal static string GetDescription(T value)
        {
            string description;
            return Descriptions.TryGetValue(value, out description)
                ? description : value.ToString();
        }
    }
}

Ответ 2

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

enum MyCountryEnum
{    
    [Description("UK")]
    UnitedKingdom = 0,    

    [Description("US")]
    UnitedStates = 1,    

    [Description("FR")]
    France = 2,    

    [Description("PO")]
    Portugal = 3
}

public static string GetDescription(this Enum value)
{
    var type = value.GetType();

    var fi = type.GetField(value.ToString());

    var descriptions = fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];

    return descriptions.Length > 0 ? descriptions[0].Description : value.ToString();
}

public static SortedDictionary<string, T> GetBoundEnum<T>() where T : struct, IConvertible
{
    // validate.
    if (!typeof(T).IsEnum)
    {
        throw new ArgumentException("T must be an Enum type.");
    }

    var results = new SortedDictionary<string, T>();

    FieldInfo[] fieldInfos = typeof(T).GetFields();

    foreach (var fi in fieldInfos)
    {

        var value = (T)fi.GetValue(fi);
        var description = GetDescription((Enum)fi.GetValue(fi));

        if (!results.ContainsKey(description))
        {
            results.Add(description, value);
        }
    }
    return results;
}

И затем, чтобы получить список связанных перечислений, его просто вызов

GetBoundEnum<MyCountryEnum>()

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

string whatever = MyCountryEnum.UnitedKingdom.GetDescription();

Ответ 3

Вы можете создать метод расширения public static string ToShortString(this Country country). В методе вы можете использовать либо статический словарь, как предлагает Джон, либо просто сделать случай переключения.

Пример:

public static class CountryExtensions
{
    public static string ToShortString( this Country target )
    {
        switch (target) {
            case Country.UnitedKingdom:
                return "UK";
            case Country.UnitedStates:
                return "US";
            case Country.France:
                return "FR";
            case Country.Portugal:
                return "PT";
            default:
                return "None";
        }
    }
}

Ответ 4

Псевдокод:

enum MyCountryEnum
{
    UnitedKingdom = 0,
    UnitedStates = 1,
    France = 2,
    Portugal = 3,
}

string[] shortCodes = new string[] {"UK", "US", "FR", "PO"};


MyCountryEnum enumValue = MyCountryEnum.UnitedKingdom;
string code = shortCodes[enumValue];

Ответ 5

Еще одна возможность, о которой не упоминалось, выглядит примерно так:

public class Country
{
    public static readonly Country UnitedKingdom = new Country("UK");
    public static readonly Country UnitedStates = new Country("US");
    public static readonly Country France = new Country("FR");
    public static readonly Country Protugal = new Country("PT");

    private Country(string shortName)
    {
        ShortName = shortName;
    }

    public string ShortName { get; private set; }
}

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

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

Ответ 6

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

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

public class Country
{
    public const string UnitedKingdom = "UK";
    public const string France = "F";
}

Таким образом, я могу использовать Country.UnitedKingdom в моем коде, и будет использоваться значение "UK".

Я просто отправляю этот ответ в качестве альтернативного решения.

Neil

Ответ 7

Просто используйте DescriptionAttribute

Нет необходимости создавать словарь, если вам нужно только получить представление String для ваших значений перечисления. См. Этот пример

[EDIT] О... забыл упомянуть, что он более многоразовый, чем словари, так как вам нужен только один общий класс util, чтобы помочь получить описание, а затем все, что вам нужно сделать, это добавить атрибут DescriptionAttribute при следующем добавлении значение enum или вы создаете новое перечисление с теми же требованиями. В решении словарь/коммутатор его сложнее поддерживать, и он становится беспорядочным, когда у вас много типов перечислений.

Ответ 8

Я попытался отправить редактирование в ответ Скотта Айви, но он был отклонен, вот еще один ответ. Мои относительно небольшие изменения:

1) Я исправил ошибку Alex System.ArgumentException: Field 'value__' defined on type 'MyClass.EnumHelperTest+MyCountryEnum' is not a field on the target object which is of type 'System.Reflection.RtFieldInfo'. Получил ее от здесь.

2) Добавлен return, поэтому вы можете скопировать/вставить его, и он будет работать.

3) Изменен SortedDictionary в словаре, потому что SortedDictionary всегда сортирует по ключу, в этом случае строка Описание. Нет никаких оснований для алфавита описания. Фактически, сортировка уничтожает первоначальный порядок перечисления. Словарь также не сохраняет порядок перечисления, но по крайней мере он не подразумевает порядок, например SortedDictionary.

enum MyCountryEnum
{    
    [Description("UK")]
    UnitedKingdom = 0,    

    [Description("US")]
    UnitedStates = 1,    

    [Description("FR")]
    France = 2,    

    [Description("PO")]
    Portugal = 3
}

public static string GetDescription(this Enum value)
{
    var type = value.GetType();

    var fi = type.GetField(value.ToString());

    var descriptions = fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];

    return descriptions.Length > 0 ? descriptions[0].Description : value.ToString();
}

public static Dictionary<string, T> GetBoundEnum<T>() where T : struct, IConvertible
{
    // validate.
    if (!typeof(T).IsEnum)
    {
        throw new ArgumentException("T must be an Enum type.");
    }

    var results = new Dictionary<string, T>();

    FieldInfo[] fieldInfos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static);

    foreach (var fi in fieldInfos)
    {

        var value = (T)fi.GetValue(fi);
        var description = GetDescription((Enum)fi.GetValue(fi));

        if (!results.ContainsKey(description))
        {
            results.Add(description, value);
        }
    }
    return results;
}

Ответ 9

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

Почему downvotes? Я думаю, что довольно широко признано, что использование полиморфного подхода лучше, чем использование перечисления. Существует нулевая причина использовать перечисление, если вы можете использовать конструкцию ValueObject.

Вот хорошее сообщение в блоге по теме: http://devpinoy.org/blogs/cruizer/archive/2007/09/12/enums-are-evil.aspx

Ответ 10

var codes = new Dictionary<Country, string>() 
        { { Country.UnitedKingdom, "UK" },
        { Country.UnitedStates, "US" },
        { Country.France, "FR" } };
Console.WriteLine(codes[Country.UnitedStates]);

Ответ 11

Следующее решение работает (компилируется и запускается). Я вижу два вопроса:

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

  • Вы полагались бы на то, что перечисления не являются безопасными в .NET.

    enum Country
    {
        UnitedKingdom = 0,
        UnitedStates = 1,
        France = 2,
        Portugal = 3
    }
    
    enum CountryCode
    {
        UK = 0,
        US = 1,
        FR = 2,
        PT = 3
    }
    
    void Main()
    {
        string countryCode = ((CountryCode)Country.UnitedKingdom).ToString();
        Console.WriteLine(countryCode);
        countryCode = ((CountryCode)Country.Portugal).ToString();
        Console.WriteLine(countryCode);
    }