如何为枚举提供用户友好名称?

时间:2009-08-25 22:59:57

标签: c# .net localization enums naming-conventions

我有一个类似

的枚举
Enum Complexity
{
  NotSoComplex,
  LittleComplex,
  Complex,
  VeryComplex
}

我想在下拉列表中使用它,但不希望在列表中看到这样的Camel名称(对用户来说看起来很奇怪)。相反,我想用正常的措辞,比如 没那么复杂 有点复杂(等)

另外,我的应用程序是多语言的,我希望能够显示这些字符串本地化,并且我使用帮助器,TranslationHelper(字符串strID),它为我提供了字符串id的本地化版本。

我有一个有效的解决方案,但不是很优雅: 我为枚举创建了一个辅助类,其中一个成员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;
    }
}

虽然可行,但我对此并不满意,因为我必须对我需要在选项列表中使用的各种枚举进行类似编码。

是否有人建议使用更简单或更通用的解决方案?

由于 波格丹

4 个答案:

答案 0 :(得分:62)

基本友好名称

使用Description attribute: *

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中有一个完善的模式,用于处理每个字符串值的多种语言 - 使用resource file,并展开扩展方法以从资源文件中读取:

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()并提供一个lambda,它使用两个字段(描述和值)投影一个新对象。

DataTextField和DataValueField属性在数据绑定时反复评估,因此它们将搜索具有匹配名称的DataItem上的字段。

- 请注意,在主要文章中,作者编写了自己的DescriptionAttribute类,这是不必要的,因为.NET的标准库中已存在这个类。 -

答案 1 :(得分:4)

在其他答案中使用属性是一种很好的方法,但如果您只想使用枚举值中的文本,则以下代码将根据值的camel-casing进行拆分:

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属性但未找到本地化文本,则默认使用camel-case拆分方法
  • 如果枚举没有定义LocalisedEnum属性,它将使用camel-case拆分方法
  • 发生任何错误时,默认为枚举值的ToString

答案 2 :(得分:1)

我使用以下课程

    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.");
    }

其中读取名为description

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

答案 3 :(得分: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之外的所有内容(添加 - 编辑模块)和其他时间来显示所有(在过滤器中)

这就是我的所作所为:

  1. 我创建了一个扩展方法,它使用Description Attribute作为本地化查找键。如果缺少Description属性,我将查找本地化密钥创建为EnumName_ EnumValue。最后,如果缺少翻译,我只需将基于camelcase的枚举名称拆分为单独的单词,如adrianbanks所示。 BTW,TranslationHelper是resourceMgr.GetString(...)
  2. 的包装器

    完整代码如下所示

    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&gt;的通用静态方法,如下所示

    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;