Как использовать имена пользователей для перечислений?

У меня есть перечисление вроде

Enum Complexity
{
  NotSoComplex,
  LittleComplex,
  Complex,
  VeryComplex
}

И я хочу использовать его в раскрывающемся списке, но не хочу видеть такие имена Camel в списке (выглядит очень странно для пользователей). Вместо этого я хотел бы иметь нормальную формулировку, например Не так сложно Маленький комплекс (и т.д.)

Кроме того, мое приложение является многоязычным, и я хотел бы иметь возможность отображать те строки, которые были локализованы, и я использую хелпер, TranslationHelper (string strID), который дает мне локализованную версию для идентификатора строки.

У меня есть рабочее решение, но не очень элегантное: Я создаю вспомогательный класс для перечисления, с одним членом Complexity и ToString(), перезаписанным, как показано ниже (упрощен код)

public class ComplexityHelper
{
    public ComplexityHelper(Complexity c, string desc)
    { m_complex = c; m_desc=desc; }

    public Complexity Complexity { get { ... } set {...} }
    public override ToString() { return m_desc; }

    //Then a static field like this 

    private static List<Complexity> m_cxList = null;

    // and method that returns the status lists to bind to DataSource of lists
    public static List<ComplexityHelper> GetComplexities() 
    {
        if (m_cxList == null)
        {
           string[] list = TranslationHelper.GetTranslation("item_Complexities").Split(',');
           Array listVal = Enum.GetValues(typeof(Complexities));
           if (list.Length != listVal.Length)
               throw new Exception("Invalid Complexities translations (item_Complexities)");
           m_cxList = new List<Complexity>();
           for (int i = 0; i < list.Length; i++)
           {
             Complexity cx = (ComplexitylistVal.GetValue(i);
             ComplexityHelper ch = new ComplexityHelper(cx, list[i]);
             m_cxList.Add(ch);
           }
        }
        return m_cxList;
    }
}

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

Есть ли у кого-нибудь предложение для более простого или более общего решения?

Спасибо Богдан

Ответ 1

Спасибо всем за ответы. Наконец, я использовал комбинацию из Rex M и adrianbanks и добавил свои собственные улучшения, чтобы упростить привязку к ComboBox.

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

Enum Complexity
{
  // this will be used in filters, 
  // but not in module where I have to assign Complexity to a field
  AllComplexities,  
  NotSoComplex,
  LittleComplex,
  Complex,
  VeryComplex
}

Поэтому иногда я хочу, чтобы список выбора отображал все, кроме AllComplexities (в дополнительных модулях редактирования) и в другое время, чтобы показать все (в фильтрах)

Вот что я сделал:

  • Я создал метод расширения, который использует атрибут описания как ключ поиска локализации. Если атрибут описания отсутствует, я создаю ключ локализации поиска как EnumName_ EnumValue. Наконец, если перевод отсутствует, я просто разбил имя enum на основе camelcase для разделения слов, как показано adrianbanks. BTW, TranslationHelper - обертка вокруг resourceMgr.GetString(...)

Полный код показан ниже

public static string GetDescription(this System.Enum value)
{
    string enumID = string.Empty;
    string enumDesc = string.Empty;
    try 
    {         
        // try to lookup Description attribute
        FieldInfo field = value.GetType().GetField(value.ToString());
        object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true);
        if (attribs.Length > 0)
        {
            enumID = ((DescriptionAttribute)attribs[0]).Description;
            enumDesc = TranslationHelper.GetTranslation(enumID);
        }
        if (string.IsNullOrEmpty(enumID) || TranslationHelper.IsTranslationMissing(enumDesc))
        {
            // try to lookup translation from EnumName_EnumValue
            string[] enumName = value.GetType().ToString().Split('.');
            enumID = string.Format("{0}_{1}", enumName[enumName.Length - 1], value.ToString());
            enumDesc = TranslationHelper.GetTranslation(enumID);
            if (TranslationHelper.IsTranslationMissing(enumDesc))
                enumDesc = string.Empty;
        }

        // try to format CamelCase to proper names
        if (string.IsNullOrEmpty(enumDesc))
        {
            Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);
            enumDesc = capitalLetterMatch.Replace(value.ToString(), " $&");
        }
    }
    catch (Exception)
    {
        // if any error, fallback to string value
        enumDesc = value.ToString();
    }

    return enumDesc;
}

Я создал общий вспомогательный класс на основе Enum, который позволяет легко связывать перечисление с DataSource

public class LocalizableEnum
{
    /// <summary>
    /// Column names exposed by LocalizableEnum
    /// </summary>
    public class ColumnNames
    {
        public const string ID = "EnumValue";
        public const string EntityValue = "EnumDescription";
    }
}

public class LocalizableEnum<T>
{

    private T m_ItemVal;
    private string m_ItemDesc;

    public LocalizableEnum(T id)
    {
        System.Enum idEnum = id as System.Enum;
        if (idEnum == null)
            throw new ArgumentException(string.Format("Type {0} is not enum", id.ToString()));
        else
        {
            m_ItemVal = id;
            m_ItemDesc = idEnum.GetDescription();
        }
    }

    public override string ToString()
    {
        return m_ItemDesc;
    }

    public T EnumValue
    {
        get { return m_ID; }
    }

    public string EnumDescription
    {
        get { return ToString(); }
    }

}

Затем я создал общий статический метод, который возвращает List > , как показано ниже

public static List<LocalizableEnum<T>> GetEnumList<T>(object excludeMember)
{
    List<LocalizableEnum<T>> list =null;
    Array listVal = System.Enum.GetValues(typeof(T));
    if (listVal.Length>0)
    {
        string excludedValStr = string.Empty;
        if (excludeMember != null)
            excludedValStr = ((T)excludeMember).ToString();

        list = new List<LocalizableEnum<T>>();
        for (int i = 0; i < listVal.Length; i++)
        {
            T currentVal = (T)listVal.GetValue(i);
            if (excludedValStr != currentVal.ToString())
            {
                System.Enum enumVal = currentVal as System.Enum;
                LocalizableEnum<T> enumMember = new LocalizableEnum<T>(currentVal);
                list.Add(enumMember);
            }
        }
    }
    return list;
}

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

public static List<LocalizableEnum<T>> GetEnumList<T>()
{
        return GetEnumList<T>(null);
}

Теперь давайте поместим все вещи и привязаем их к фактическому комбо:

// in module where we want to show items with all complexities
// or just filter on one complexity

comboComplexity.DisplayMember = LocalizableEnum.ColumnNames.EnumValue;
comboComplexity.ValueMember = LocalizableEnum.ColumnNames.EnumDescription;
comboComplexity.DataSource = EnumHelper.GetEnumList<Complexity>();
comboComplexity.SelectedValue = Complexity.AllComplexities;

// ....
// and here in edit module where we don't want to see "All Complexities"
comboComplexity.DisplayMember = LocalizableEnum.ColumnNames.EnumValue;
comboComplexity.ValueMember = LocalizableEnum.ColumnNames.EnumDescription;
comboComplexity.DataSource = EnumHelper.GetEnumList<Complexity>(Complexity.AllComplexities);
comboComplexity.SelectedValue = Complexity.VeryComplex; // set default value

Чтобы прочитать выбранное значение и использовать его, я использую код ниже

Complexity selComplexity = (Complexity)comboComplexity.SelectedValue;

Ответ 2

Основные дружественные имена

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

enum MyEnum
{
    [Description("This is black")]
    Black,
    [Description("This is white")]
    White
}

И удобный метод расширения для перечислений:

public static string GetDescription(this Enum value)
{
    FieldInfo field = value.GetType().GetField(value.ToString());
    object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true);
    if(attribs.Length > 0)
    {
        return ((DescriptionAttribute)attribs[0]).Description;
    }
    return string.Empty;
}

Используется так:

MyEnum val = MyEnum.Black;
Console.WriteLine(val.GetDescription()); //writes "This is black"

(Обратите внимание, что это точно не работает для битовых флагов...)

Для локализации

В .NET существует хорошо установленный шаблон для обработки нескольких языков на строковое значение - используйте файл ресурсов и разверните метод расширения для чтения из файла ресурсов:

public static string GetDescription(this Enum value)
{
    FieldInfo field = value.GetType().GetField(value.ToString());
    object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true));
    if(attribs.Length > 0)
    {
        string message = ((DescriptionAttribute)attribs[0]).Description;
        return resourceMgr.GetString(message, CultureInfo.CurrentCulture);
    }
    return string.Empty;
}

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

Объединяя все это

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

<asp:DropDownList ID="myDDL"
                  DataTextField="Description"
                  DataValueField="Value" />

myDDL.DataSource = Enum.GetValues(typeof(MyEnum)).OfType<MyEnum>().Select(
    val => new { Description = val.GetDescription(), Value = val.ToString() });

myDDL.DataBind();

Позвольте сломать эту строку DataSource:

  • Сначала мы вызываем Enum.GetValues(typeof(MyEnum)), что дает нам слабо типизированный Array значений
  • Затем мы вызываем OfType<MyEnum>(), который преобразует массив в IEnumerable<MyEnum>
  • Затем мы вызываем Select() и предоставляем лямбду, которая проектирует новый объект с двумя полями: Описание и значение.

Свойства DataTextField и DataValueField оцениваются рефлексивно во время привязки данных, поэтому они будут искать поля в DataItem с соответствующими именами.

-Примечание в основной статье автор написал собственный класс DescriptionAttribute, который не нужен, поскольку он уже существует в стандартных библиотеках .NET.-

Ответ 3

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

public static string GetDescriptionOf(Enum enumType)
{
    Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);
    return capitalLetterMatch.Replace(enumType.ToString(), " $&");
}

Вызов GetDescriptionOf(Complexity.NotSoComplex) вернет Not So Complex. Это можно использовать с любым значением перечисления.

Чтобы сделать его более полезным, вы можете сделать его методом расширения:

public static string ToFriendlyString(this Enum enumType)
{
    Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);
    return capitalLetterMatch.Replace(enumType.ToString(), " $&");
}

Теперь вы вызываете его с помощью Complexity.NotSoComplex.ToFriendlyString() для возврата Not So Complex.


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

enum Complexity
{
    [LocalisedEnum("Complexity.NotSoComplex")]
    NotSoComplex,
    [LocalisedEnum("Complexity.LittleComplex")]
    LittleComplex,
    [LocalisedEnum("Complexity.Complex")]
    Complex,
    [LocalisedEnum("Complexity.VeryComplex")]
    VeryComplex
}

Вам также понадобится этот код:

[AttributeUsage(AttributeTargets.Field, AllowMultiple=false, Inherited=true)]
public class LocalisedEnum : Attribute
{
    public string LocalisationKey{get;set;}

    public LocalisedEnum(string localisationKey)
    {
        LocalisationKey = localisationKey;
    }
}

public static class LocalisedEnumExtensions
{
    public static string ToLocalisedString(this Enum enumType)
    {
        // default value is the ToString();
        string description = enumType.ToString();

        try
        {
            bool done = false;

            MemberInfo[] memberInfo = enumType.GetType().GetMember(enumType.ToString());

            if (memberInfo != null && memberInfo.Length > 0)
            {
                object[] attributes = memberInfo[0].GetCustomAttributes(typeof(LocalisedEnum), false);

                if (attributes != null && attributes.Length > 0)
                {
                    LocalisedEnum descriptionAttribute = attributes[0] as LocalisedEnum;

                    if (description != null && descriptionAttribute != null)
                    {
                        string desc = TranslationHelper.GetTranslation(descriptionAttribute.LocalisationKey);

                        if (desc != null)
                        {
                            description = desc;
                            done = true;
                        }
                    }
                }
            }

            if (!done)
            {
                Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);
                description = capitalLetterMatch.Replace(enumType.ToString(), " $&");
            }
        }
        catch
        {
            description = enumType.ToString();
        }

        return description;
    }
}

Чтобы получить локализованные описания, вы должны вызвать:

Complexity.NotSoComplex.ToLocalisedString()

Это имеет несколько резервных случаев:

  • если перечисление имеет атрибут LocalisedEnum, он будет использовать ключ для поиска переведенного текста
  • если перечисление имеет атрибут LocalisedEnum, но локализованный текст не найден, по умолчанию используется метод разметки на верблюдах
  • Если в перечислении не указан атрибут LocalisedEnum, он будет использовать метод разбиения на верблюжку
  • при любой ошибке, по умолчанию используется значение ToString значения enum

Ответ 4

Я использую следующий класс

    public class EnumUtils
    {
    /// <summary>
    ///     Reads and returns the value of the Description Attribute of an enumeration value.
    /// </summary>
    /// <param name="value">The enumeration value whose Description attribute you wish to have returned.</param>
    /// <returns>The string value portion of the Description attribute.</returns>
    public static string StringValueOf(Enum value)
    {
        FieldInfo fi = value.GetType().GetField(value.ToString());
        DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attributes.Length > 0)
        {
            return attributes[0].Description;
        }
        else
        {
            return value.ToString();
        }
    }

    /// <summary>
    ///     Returns the Enumeration value that has a given Description attribute.
    /// </summary>
    /// <param name="value">The Description attribute value.</param>
    /// <param name="enumType">The type of enumeration in which to search.</param>
    /// <returns>The enumeration value that matches the Description value provided.</returns>
    /// <exception cref="ArgumentException">Thrown when the specified Description value is not found with in the provided Enumeration Type.</exception>
    public static object EnumValueOf(string value, Type enumType)
    {
        string[] names = Enum.GetNames(enumType);
        foreach (string name in names)
        {
            if (StringValueOf((Enum)Enum.Parse(enumType, name)).Equals(value))
            {
                return Enum.Parse(enumType, name);
            }
        }

        throw new ArgumentException("The string is not a description or value of the specified enum.");
    }

Что читает атрибут, называемый описанием

public enum PuppyType
{
    [Description("Cute Puppy")]
    CutePuppy = 0,
    [Description("Silly Puppy")]
    SillyPuppy
}