C # Получение значений Enum - PullRequest
30 голосов
/ 17 июня 2009

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

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

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

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

Ответы [ 11 ]

53 голосов
/ 17 июня 2009

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

Два варианта предлагают себя:

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

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

Пример кода для 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();

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

Смена оператора

Я согласен с ответом 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();
    }
}

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

DescriptionAttribute

Точка Радо сделал о том, что код для 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();
        }
    }
}
23 голосов
/ 17 июня 2009

Я предпочитаю использовать 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();
15 голосов
/ 17 июня 2009

Вы можете создать метод расширения 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";
        }
    }
}
6 голосов
/ 17 июня 2009

Псевдокод:

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];
3 голосов
/ 02 июля 2009

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

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

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

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

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

Neil

3 голосов
/ 17 июня 2009

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

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

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

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

2 голосов
/ 28 января 2016

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

1) Я исправил ошибку Алекса в 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 всегда сортирует по ключу, в данном случае строку Description. Нет причин алфавитизировать описание. Фактически, сортировка уничтожает первоначальный порядок перечисления. Словарь также не сохраняет порядок перечисления, но, по крайней мере, он не подразумевает порядок, как в 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;
}
2 голосов
/ 17 июня 2009

Просто используйте Описание атрибута

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

[РЕДАКТИРОВАТЬ] Ох ... забыл упомянуть, что он более пригоден для повторного использования, чем словари, так как вам нужен только один общий класс утилит, чтобы помочь получить описание, а затем все, что вам нужно сделать, это добавить Атрибут Description в следующий раз, когда вы добавите значение перечисления или вы создаете новое перечисление с такими же требованиями. В решении для словаря / переключателя его сложнее поддерживать, и оно становится грязным, если у вас много типов перечислений.

1 голос
/ 17 июня 2009

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

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

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

0 голосов
/ 17 июня 2009

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

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

  2. Вы полагаетесь на тот факт, что перечисления не являются типобезопасными в .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);
    }
    
...