Строковое представление Enum - PullRequest
875 голосов
/ 08 января 2009

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

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

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

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

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

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

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

    public string Value
    {
        get { return _value; }
    }

}

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

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

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

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

        //Check first in our cached results...

        //Look for our 'StringValueAttribute' 

        //in the field's custom attributes

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

        return output;
    }
}

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

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

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

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

Ответы [ 35 ]

851 голосов
/ 08 января 2009

Попробуйте type-safe-enum pattern.

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>();
    
    • n.b. Чтобы инициализация полей «член enum» не вызывала исключение NullReferenceException при вызове конструктора экземпляра, обязательно поместите поле «Словарь» перед полями «член enum» в своем классе. Это связано с тем, что инициализаторы статического поля вызываются в порядке объявления и перед статическим конструктором, создавая странную и необходимую, но запутанную ситуацию, когда конструктор экземпляра можно вызвать до того, как все статические поля были инициализированы, и до вызова статического конструктора. 1017 *
  • заполнение этого отображения в конструкторе экземпляра

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

    public static explicit operator AuthenticationMethod(string str)
    {
        AuthenticationMethod result;
        if (instance.TryGetValue(str, out result))
            return result;
        else
            throw new InvalidCastException();
    }
    
222 голосов
/ 08 января 2009

Использовать метод

Enum.GetName(Type MyEnumType,  object enumvariable)  

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

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

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

77 голосов
/ 08 января 2009

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

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

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

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

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

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

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

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

Выходы:

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

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

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

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

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

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());
66 голосов
/ 08 января 2009

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

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

.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"
57 голосов
/ 11 февраля 2012

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

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".

39 голосов
/ 06 сентября 2012

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

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

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

any.Tomato.ToString();
27 голосов
/ 22 сентября 2015

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

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

Чтобы получить строку, просто используйте:

MyStatus.Active.ToString("f");

или

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

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

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

27 голосов
/ 08 января 2009

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

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

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

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

        }

Как пример:

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

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

24 голосов
/ 21 января 2014

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

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

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

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

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

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

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

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;      
}
12 голосов
/ 08 января 2009

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

В любом случае, использование включает преобразователи типов, поэтому, если вы привязываетесь к пользовательскому интерфейсу, он «просто работает». Вы можете расширить шаблон 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);
    }
}

}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...