如何为枚举使用用户友好的名称? [复制]

Posted

技术标签:

【中文标题】如何为枚举使用用户友好的名称? [复制]【英文标题】:How to have userfriendly names for enumerations? [duplicate] 【发布时间】:2009-08-25 22:59:56 【问题描述】:

我有一个像

这样的枚举
Enum Complexity

  NotSoComplex,
  LittleComplex,
  Complex,
  VeryComplex

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

另外,我的应用程序是多语言的,我希望能够显示那些本地化的字符串,我使用了一个助手 TranslationHelper(string 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;
    

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

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

谢谢 波格丹

【问题讨论】:

见***.com/questions/569298/…> 【参考方案1】:

基本友好名称

使用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&lt;MyEnum&gt;(),它将数组转换为IEnumerable&lt;MyEnum&gt; 然后我们调用 Select() 并提供一个 lambda,该 lambda 投影具有两个字段(描述和值)的新对象。

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

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

【讨论】:

对于本地化,您可以跳过描述属性,只使用“MyEnum.Black”和“MyEnum.White”作为资源名称。 @stevemegson 那肯定行得通!不过,我是一个大 SoC/SRP 纯粹主义者,对我来说,将枚举作为编程值和多语言资源键的双重用途引发了警钟 :) 嗨,我想要完成的是使用像 comboComplexity.DataSource = Enum.GetValues(typeof(Complexity)); 这样的绑定。但是使用它我仍然可以在组合中获得枚举值的名称,而不是它们的描述此外,我将 GetDescription 重命名为 ToString(),希望组合将其用于枚举名称,但没有运气任何建议?谢谢 @bzamfir 在答案底部查看我的附加说明。 添加了一个扩展方法以方便添加到下拉列表:myDropDown.Items.AddEnumDescriptions&lt;myEnumType&gt;();public static void AddEnumDescriptions&lt;T&gt;(this ListItemCollection value) foreach (var enumvalue in System.Enum.GetValues(typeof(T)).OfType&lt;Enum&gt;()) value.Add(new ListItem(enumvalue.GetDescription(), enumvalue.ToString())); 【参考方案2】:

在其他答案中使用属性是一个不错的方法,但是如果您只想使用枚举值中的文本,则以下代码将根据值的驼峰式大小写进行拆分:

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


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

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

【讨论】:

您好,感谢您的回答。我尝试了您的建议,并尝试使用类似 comboComplexity.DataSource = Enum.GetValues(typeof(Complexity)); 的代码将组合绑定到枚举。但这使得列表只显示默认枚举名称,我也将 ToLocalizedString() 重命名为 ToString() (知道这实际上是由 Combo 调用的),但仍然没有用。有什么建议吗?我想要一个像这样的简单绑定,因为枚举有 20 多个值,将所有值一个一个地添加会很痛苦谢谢 通过将 ToLocalizedString() 重命名为 ToString(),您并没有覆盖枚举的 ToString() 方法。相反,您创建了一个与枚举的现有 ToString() 方法同名的扩展方法。调用 ToString() 将调用现有方法而不是扩展方法。您必须获取每个枚举值的本地化字符串并将数据绑定到该组字符串。【参考方案3】:

我使用下面的类

    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

【讨论】:

【参考方案4】:

感谢大家的所有回答。 最后,我使用了 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_ 枚举值。最后,如果缺少翻译,我只是将基于驼峰式的枚举名称拆分为分隔单词,如 adrianbanks 所示。顺便说一句,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;

【讨论】:

以上是关于如何为枚举使用用户友好的名称? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何为 Visual Basic 6 ActiveX CLSID 设置“友好”名称

如何为枚举对象实现增量函数? [复制]

System.Text.Json:如何为枚举值指定自定义名称?

如何为 Spring Security 创建类型安全的用户角色?

如何为文件类型创建所有文件关联(友好的应用程序名称和可执行文件)的 C# 列表

如何为 GET 请求正确配置 Apache 重写规则?