我的枚举可以有友好的名字吗?
我有以下enum
public enum myEnum { ThisNameWorks, This Name doesn't work Neither.does.this; }
用“友好名称”来enum
是不可能的?
枚举值名称必须遵循与C#中所有标识符相同的命名规则,因此只有名字是正确的。
正如Yuriy所build议的那样,您可以使用Description
属性。 下面的扩展方法可以很容易的得到枚举的给定值的描述:
public static string GetDescription(this Enum value) { Type type = value.GetType(); string name = Enum.GetName(type, value); if (name != null) { FieldInfo field = type.GetField(name); if (field != null) { DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute; if (attr != null) { return attr.Description; } } } return null; }
你可以这样使用它:
public enum MyEnum { [Description("Description for Foo")] Foo, [Description("Description for Bar")] Bar } MyEnum x = MyEnum.Foo; string description = x.GetDescription();
如果您有以下枚举:
public enum MyEnum { First, Second, Third }
你可以为MyEnum
声明扩展方法(就像你可以为任何其他types一样)。 我只是鞭打了这个:
namespace Extension { public static class ExtensionMethods { public static string EnumValue(this MyEnum e) { switch (e) { case MyEnum.First: return "First Friendly Value"; case MyEnum.Second: return "Second Friendly Value"; case MyEnum.Third: return "Third Friendly Value"; } return "Horrible Failure!!"; } } }
有了这个扩展方法,下面现在是合法的:
Console.WriteLine(MyEnum.First.EnumValue());
希望这可以帮助!!
不,但是您可以使用DescriptionAttribute来完成要查找的内容。
这个技巧的一个问题是description属性不能被本地化。 我喜欢Sacha Barber的一种技术,在这里他创build了自己的Description属性版本,它将从相应的资源pipe理器中获取值。
http://www.codeproject.com/KB/WPF/FriendlyEnums.aspx
尽pipe本文讨论的是WPF开发人员绑定枚举时通常面临的问题,但您可以直接跳转到创buildLocalizableDescriptionAttribute的部分。
你可以使用描述属性来获得友好的名字。 你可以使用下面的代码:
/// <summary> /// Very good method to Override ToString on Enums /// Case : Suppose your enum value is EncryptionProviderType and you want /// enumVar.Tostring() to retrun "Encryption Provider Type" then you should use this method. /// Prerequisite : All enum members should be applied with attribute [Description("String to be returned by Tostring()")] /// Example : /// enum ExampleEnum /// { /// [Description("One is one")] /// ValueOne = 1, /// [Description("Two is two")] /// ValueTow = 2 /// } /// /// in your class /// ExampleEnum enumVar = ExampleEnum.ValueOne ; /// Console.WriteLine(ToStringEnums(enumVar)); /// </summary> /// <param name="en"></param> /// <returns></returns> public static string ToStringEnums(Enum en) { Type type = en.GetType(); MemberInfo[] memInfo = type.GetMember(en.ToString()); if (memInfo != null && memInfo.Length > 0) { object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); if (attrs != null && attrs.Length > 0) return ((DescriptionAttribute)attrs[0]).Description; } return en.ToString(); }
一些很好的解决scheme已经发布。 当我遇到这个问题的时候,我想去两种方式:将一个枚举转换成一个描述,并将匹配一个描述的string转换为一个枚举。
我有两个变种,缓慢和快速。 两者都从枚举转换为string和string为枚举。 我的问题是,我有这样的枚举,其中一些元素需要属性,有些则不需要。 我不想把属性放在不需要它们的元素上。 目前我有大约一百个:
public enum POS { CC, // Coordinating conjunction CD, // Cardinal Number DT, // Determiner EX, // Existential there FW, // Foreign Word IN, // Preposision or subordinating conjunction JJ, // Adjective [System.ComponentModel.Description("WP$")] WPDollar, //$ Possessive wh-pronoun WRB, // Wh-adverb [System.ComponentModel.Description("#")] Hash, [System.ComponentModel.Description("$")] Dollar, [System.ComponentModel.Description("''")] DoubleTick, [System.ComponentModel.Description("(")] LeftParenth, [System.ComponentModel.Description(")")] RightParenth, [System.ComponentModel.Description(",")] Comma, [System.ComponentModel.Description(".")] Period, [System.ComponentModel.Description(":")] Colon, [System.ComponentModel.Description("``")] DoubleBackTick, };
处理这个问题的第一种方法很慢,并且基于我在这里和networking上看到的build议。 这很慢,因为我们正在反思每个转换:
using System; using System.Collections.Generic; namespace CustomExtensions { /// <summary> /// uses extension methods to convert enums with hypens in their names to underscore and other variants public static class EnumExtensions { /// <summary> /// Gets the description string, if available. Otherwise returns the name of the enum field /// LthWrapper.POS.Dollar.GetString() yields "$", an impossible control character for enums /// </summary> /// <param name="value"></param> /// <returns></returns> public static string GetStringSlow(this Enum value) { Type type = value.GetType(); string name = Enum.GetName(type, value); if (name != null) { System.Reflection.FieldInfo field = type.GetField(name); if (field != null) { System.ComponentModel.DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute; if (attr != null) { //return the description if we have it name = attr.Description; } } } return name; } /// <summary> /// Converts a string to an enum field using the string first; if that fails, tries to find a description /// attribute that matches. /// "$".ToEnum<LthWrapper.POS>() yields POS.Dollar /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static T ToEnumSlow<T>(this string value) //, T defaultValue) { T theEnum = default(T); Type enumType = typeof(T); //check and see if the value is a non attribute value try { theEnum = (T)Enum.Parse(enumType, value); } catch (System.ArgumentException e) { bool found = false; foreach (T enumValue in Enum.GetValues(enumType)) { System.Reflection.FieldInfo field = enumType.GetField(enumValue.ToString()); System.ComponentModel.DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute; if (attr != null && attr.Description.Equals(value)) { theEnum = enumValue; found = true; break; } } if( !found ) throw new ArgumentException("Cannot convert " + value + " to " + enumType.ToString()); } return theEnum; } } }
这个问题是你每次都在做reflection。 我没有衡量这样做的performance,但似乎令人担忧。 更糟的是,我们正在反复计算这些昂贵的转换,而没有caching它们。
相反,我们可以使用静态构造函数来填充这些转换信息的字典,然后在需要时查找这些信息。 显然静态类(扩展方法所需的)可以有构造函数和字段:)
using System; using System.Collections.Generic; namespace CustomExtensions { /// <summary> /// uses extension methods to convert enums with hypens in their names to underscore and other variants /// I'm not sure this is a good idea. While it makes that section of the code much much nicer to maintain, it /// also incurs a performance hit via reflection. To circumvent this, I've added a dictionary so all the lookup can be done once at /// load time. It requires that all enums involved in this extension are in this assembly. /// </summary> public static class EnumExtensions { //To avoid collisions, every Enum type has its own hash table private static readonly Dictionary<Type, Dictionary<object,string>> enumToStringDictionary = new Dictionary<Type,Dictionary<object,string>>(); private static readonly Dictionary<Type, Dictionary<string, object>> stringToEnumDictionary = new Dictionary<Type, Dictionary<string, object>>(); static EnumExtensions() { //let's collect the enums we care about List<Type> enumTypeList = new List<Type>(); //probe this assembly for all enums System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); Type[] exportedTypes = assembly.GetExportedTypes(); foreach (Type type in exportedTypes) { if (type.IsEnum) enumTypeList.Add(type); } //for each enum in our list, populate the appropriate dictionaries foreach (Type type in enumTypeList) { //add dictionaries for this type EnumExtensions.enumToStringDictionary.Add(type, new Dictionary<object,string>() ); EnumExtensions.stringToEnumDictionary.Add(type, new Dictionary<string,object>() ); Array values = Enum.GetValues(type); //its ok to manipulate 'value' as object, since when we convert we're given the type to cast to foreach (object value in values) { System.Reflection.FieldInfo fieldInfo = type.GetField(value.ToString()); //check for an attribute System.ComponentModel.DescriptionAttribute attribute = Attribute.GetCustomAttribute(fieldInfo, typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute; //populate our dictionaries if (attribute != null) { EnumExtensions.enumToStringDictionary[type].Add(value, attribute.Description); EnumExtensions.stringToEnumDictionary[type].Add(attribute.Description, value); } else { EnumExtensions.enumToStringDictionary[type].Add(value, value.ToString()); EnumExtensions.stringToEnumDictionary[type].Add(value.ToString(), value); } } } } public static string GetString(this Enum value) { Type type = value.GetType(); string aString = EnumExtensions.enumToStringDictionary[type][value]; return aString; } public static T ToEnum<T>(this string value) { Type type = typeof(T); T theEnum = (T)EnumExtensions.stringToEnumDictionary[type][value]; return theEnum; } } }
看看转换方法现在有多紧张。 我能想到的唯一缺陷是这需要所有转换的枚举都在当前的程序集中。 此外,我只打扰出口枚举,但你可以改变,如果你愿意。
这是如何调用方法
string x = LthWrapper.POS.Dollar.GetString(); LthWrapper.POS y = "PRP$".ToEnum<LthWrapper.POS>();
public enum myEnum { ThisNameWorks, This_Name_can_be_used_instead, }
在阅读了包括StackOverFlow在内的许多资源后,我发现并不是所有的解决scheme都能正常工作。 以下是我们试图解决这个问题。
基本上,如果存在,我们就从一个DescriptionAttribute中取一个Enum的友好名称。
如果不是,我们使用RegEx来确定Enum名称中的单词并添加空格。
下一个版本,我们将使用另一个属性来标记我们是否可以/应该从可本地化的资源文件中取得友好名称。
以下是testing用例。 如果你有另一个testing用例不合格,请报告。
public static class EnumHelper { public static string ToDescription(Enum value) { if (value == null) { return string.Empty; } if (!Enum.IsDefined(value.GetType(), value)) { return string.Empty; } FieldInfo fieldInfo = value.GetType().GetField(value.ToString()); if (fieldInfo != null) { DescriptionAttribute[] attributes = fieldInfo.GetCustomAttributes(typeof (DescriptionAttribute), false) as DescriptionAttribute[]; if (attributes != null && attributes.Length > 0) { return attributes[0].Description; } } return StringHelper.ToFriendlyName(value.ToString()); } } public static class StringHelper { public static bool IsNullOrWhiteSpace(string value) { return value == null || string.IsNullOrEmpty(value.Trim()); } public static string ToFriendlyName(string value) { if (value == null) return string.Empty; if (value.Trim().Length == 0) return string.Empty; string result = value; result = string.Concat(result.Substring(0, 1).ToUpperInvariant(), result.Substring(1, result.Length - 1)); const string pattern = @"([AZ]+(?![az])|\d+|[AZ][az]+|(?![AZ])[az]+)+"; List<string> words = new List<string>(); Match match = Regex.Match(result, pattern); if (match.Success) { Group group = match.Groups[1]; foreach (Capture capture in group.Captures) { words.Add(capture.Value); } } return string.Join(" ", words.ToArray()); } } [TestMethod] public void TestFriendlyName() { string[][] cases = { new string[] {null, string.Empty}, new string[] {string.Empty, string.Empty}, new string[] {" ", string.Empty}, new string[] {"A", "A"}, new string[] {"z", "Z"}, new string[] {"Pascal", "Pascal"}, new string[] {"camel", "Camel"}, new string[] {"PascalCase", "Pascal Case"}, new string[] {"ABCPascal", "ABC Pascal"}, new string[] {"PascalABC", "Pascal ABC"}, new string[] {"Pascal123", "Pascal 123"}, new string[] {"Pascal123ABC", "Pascal 123 ABC"}, new string[] {"PascalABC123", "Pascal ABC 123"}, new string[] {"123Pascal", "123 Pascal"}, new string[] {"123ABCPascal", "123 ABC Pascal"}, new string[] {"ABC123Pascal", "ABC 123 Pascal"}, new string[] {"camelCase", "Camel Case"}, new string[] {"camelABC", "Camel ABC"}, new string[] {"camel123", "Camel 123"}, }; foreach (string[] givens in cases) { string input = givens[0]; string expected = givens[1]; string output = StringHelper.ToFriendlyName(input); Assert.AreEqual(expected, output); } } }
他们遵循与variables名相同的命名规则。 所以他们不应该包含空格。
另外你所暗示的将是非常糟糕的做法。
枚举名与普通variables名相同,也就是说名字中间没有空格或点…我仍然认为第一个相当友好,虽然…
这是一个可怕的想法,但它确实有效。
public enum myEnum { ThisNameWorks, ThisNameDoesntWork149141331,// This Name doesn't work NeitherDoesThis1849204824// Neither.does.this; } class Program { private static unsafe void ChangeString(string original, string replacement) { if (original.Length < replacement.Length) throw new ArgumentException(); fixed (char* pDst = original) fixed (char* pSrc = replacement) { // Update the length of the original string int* lenPtr = (int*)pDst; lenPtr[-1] = replacement.Length; // Copy the characters for (int i = 0; i < replacement.Length; i++) pDst[i] = pSrc[i]; } } public static unsafe void Initialize() { ChangeString(myEnum.ThisNameDoesntWork149141331.ToString(), "This Name doesn't work"); ChangeString(myEnum.NeitherDoesThis1849204824.ToString(), "Neither.does.this"); } static void Main(string[] args) { Console.WriteLine(myEnum.ThisNameWorks); Console.WriteLine(myEnum.ThisNameDoesntWork149141331); Console.WriteLine(myEnum.NeitherDoesThis1849204824); Initialize(); Console.WriteLine(myEnum.ThisNameWorks); Console.WriteLine(myEnum.ThisNameDoesntWork149141331); Console.WriteLine(myEnum.NeitherDoesThis1849204824); }
要求
-
您的枚举名称必须具有相同的字符数或超过您想要的string。
-
你的枚举名不应该在任何地方重复,以防stringinterning混乱了事情
为什么这是一个坏主意(几个原因)
-
由于要求,你的枚举名称变得丑陋
-
它依靠你尽早调用初始化方法
-
不安全的指针
-
如果string的内部格式发生变化,例如,如果长度字段被移动,则会被拧紧
-
如果Enum.ToString()被更改,只返回一个副本,你搞砸了
-
Raymond Chen会抱怨你使用了无证的function,在他的下一个.NET周期间,CLR团队无法进行优化以减less50%的运行时间。
我想你想显示你的枚举值给用户,所以你希望他们有一个友好的名字。 这里是我的build议:使用枚举types模式。虽然你应该做一些努力实现它,但它真的值得。
public class MyEnum { public static readonly MyEnum Enum1=new MyEnum("This will work",1); public static readonly MyEnum Enum2=new MyEnum("This.will.work.either",2); public static readonly MyEnum[] All=new []{Enum1,Enum2}; private MyEnum(string name,int value) { Name=name; Value=value; } public string Name{get;set;} public int Value{get;set;} public override string ToString() { return Name; } }