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 |
让我们分解数据源行:
- 首先我们称之为
Enum.GetValues(typeof(MyEnum)) ,它使我们得到一个松散类型的Array 值。 - 接下来我们调用
OfType ,它将数组转换为() IEnumerable 。 - 然后我们调用
Select() ,并提供一个lambda,它用两个字段(描述和值)来投影一个新对象。
DataTextField和DataValueField属性在数据绑定时进行反射评估,因此它们将搜索具有匹配名称的DataItem上的字段。
-注意:在主要文章中,作者编写了自己的
在其他答案中使用属性是一个很好的方法,但是如果您只想使用枚举值中的文本,则以下代码将基于值的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()," $&"); } |
呼叫
要使其更有用,可以将其作为扩展方法:
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()," $&"); } |
现在您可以使用
编辑:刚注意到在你的问题中,你提到你需要本地化文本。在这种情况下,我将使用一个属性来包含一个键来查找本地化的值,但如果找不到本地化的文本,则默认使用友好的字符串方法作为最后的手段。您可以这样定义枚举:
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 } |
因此,有时我希望选择列表显示除所有复杂性(在添加-编辑模块中)之外的所有内容,而其他时间显示所有内容(在过滤器中)
我是这样做的:
完整代码如下所示
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 } |