关于c#:如何为枚举提供用户友好的名称?

How to have userfriendly names for enumerations?

本问题已经有最佳答案,请猛点这里访问。

我有一个像

1
2
3
4
5
6
7
Enum Complexity
{
  NotSoComplex,
  LittleComplex,
  Complex,
  VeryComplex
}

我想在下拉列表中使用它,但不想在列表中看到这样的驼色名称(用户看起来很奇怪)。相反,我想用正常的措辞,比如不那么复杂小复杂(等)

另外,我的应用程序是multi-lang,我希望能够显示本地化的字符串,并使用一个助手translationhelper(string strid),它为字符串ID提供本地化版本。

我有一个有效的解决方案,但不是很优雅:我为枚举创建了一个助手类,其中一个成员的复杂性和toString()被覆盖,如下所示(代码简化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;
    }
}

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

有人对更简单或更通用的解决方案有什么建议吗?

谢谢波格丹


基本友好名称

使用描述属性:*

1
2
3
4
5
6
7
enum MyEnum
{
    [Description("This is black")]
    Black,
    [Description("This is white")]
    White
}

以及一种简便的枚举扩展方法:

1
2
3
4
5
6
7
8
9
10
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;
}

像这样使用:

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

(注意,这对位标志不完全适用…)

用于本地化

.NET中有一个很好的模式,用于处理每个字符串值的多种语言-使用资源文件,并展开扩展方法以读取资源文件:

1
2
3
4
5
6
7
8
9
10
11
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,我们可能需要跟踪控件中的实际枚举值,并将翻译的友好名称限制为VisualSugar。我们可以通过使用匿名类型和列表上的DataField属性来实现这一点:

1
2
3
4
5
6
7
8
<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();

让我们分解数据源行:

  • 首先我们称之为Enum.GetValues(typeof(MyEnum)),它使我们得到一个松散类型的Array值。
  • 接下来我们调用OfType(),它将数组转换为IEnumerable
  • 然后我们调用Select(),并提供一个lambda,它用两个字段(描述和值)来投影一个新对象。

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

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


在其他答案中使用属性是一个很好的方法,但是如果您只想使用枚举值中的文本,则以下代码将基于值的camel大小写拆分:

1
2
3
4
5
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。这可以与任何枚举值一起使用。

要使其更有用,可以将其作为扩展方法:

1
2
3
4
5
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

编辑:刚注意到在你的问题中,你提到你需要本地化文本。在这种情况下,我将使用一个属性来包含一个键来查找本地化的值,但如果找不到本地化的文本,则默认使用友好的字符串方法作为最后的手段。您可以这样定义枚举:

1
2
3
4
5
6
7
8
9
10
11
enum Complexity
{
    [LocalisedEnum("Complexity.NotSoComplex")]
    NotSoComplex,
    [LocalisedEnum("Complexity.LittleComplex")]
    LittleComplex,
    [LocalisedEnum("Complexity.Complex")]
    Complex,
    [LocalisedEnum("Complexity.VeryComplex")]
    VeryComplex
}

您还需要此代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
[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;
    }
}

要获得本地化描述,您可以调用:

1
Complexity.NotSoComplex.ToLocalisedString()

这有几个回退案例:

  • 如果枚举定义了LocalisedEnum属性,它将使用键查找翻译后的文本。
  • 如果枚举定义了LocalisedEnum属性,但未找到本地化文本,则默认使用camel case split方法
  • 如果枚举没有定义LocalisedEnum属性,它将使用camel case split方法
  • 出现任何错误时,它默认为枚举值的ToString


谢谢大家的回答。最后,我使用了Rex M和Adrianbanks的组合,并添加了我自己的改进,以简化到ComboBox的绑定。

需要进行更改,因为在处理代码时,我意识到有时我需要从组合中排除一个枚举项。例如。

1
2
3
4
5
6
7
8
9
10
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
}

因此,有时我希望选择列表显示除所有复杂性(在添加-编辑模块中)之外的所有内容,而其他时间显示所有内容(在过滤器中)

我是这样做的:

  • 我创建了一个扩展方法,它使用描述属性作为本地化查找键。如果缺少description属性,我将查找本地化键创建为enumname_枚举值。最后,如果缺少翻译,我只需根据camelcase拆分枚举名称,以分离单词,如Adrianbanks所示。顺便说一句,translationhelper是围绕resourcemgr.getstring(…)的包装器。
  • 完整代码如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    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;
    }

    我创建了一个基于枚举的通用助手类,它允许轻松地将枚举绑定到数据源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    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>的通用静态方法,如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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;
    }

    以及一个包装器,用于返回包含所有成员的列表

    1
    2
    3
    4
    public static List<LocalizableEnum<T>> GetEnumList<T>()
    {
            return GetEnumList<T>(null);
    }

    现在让我们把所有的东西放在一起并绑定到实际的组合:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 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

    要读取选定的值并使用它,我使用下面的代码

    1
    Complexity selComplexity = (Complexity)comboComplexity.SelectedValue;

    我用下节课

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
        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的属性

    1
    2
    3
    4
    5
    6
    7
    public enum PuppyType
    {
        [Description("Cute Puppy")]
        CutePuppy = 0,
        [Description("Silly Puppy")]
        SillyPuppy
    }