我的枚举可以有友好的名称吗?

我有以下enum

public enum myEnum
{
ThisNameWorks,
This Name doesn't work
Neither.does.this;
}

是否不能使用“友好名称”enum

199805 次浏览

它们遵循与变量名相同的命名规则。 因此,它们不应包含空格。

而且,你的建议无论如何都是非常糟糕的做法。

枚举值名称必须遵循与C#中所有标识符相同的命名规则,因此只有名字是正确的。

枚举名称遵循与普通变量名称相同的规则,即名称中间没有空格或圆点。我仍然认为第一个是相当友好的……

public enum myEnum
{
ThisNameWorks,
This_Name_can_be_used_instead,


}

不,但您可以使用描述属性来完成您正在寻找的内容。

您可以使用Description属性来获取该友好名称。您可以使用以下代码:

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();
}

您希望使用此方法的示例:当您的枚举值EncryptionProviderType时,您希望enumVar.Tostring()返回“加密提供程序类型”。

先决条件:所有枚举成员都应应用ABC0__属性。

示例枚举:

enum ExampleEnum
{
[Description("One is one")]
ValueOne = 1,
[Description("Two is two")]
ValueTow = 2
}

在你的课堂上,你会这样使用它:

ExampleEnum enumVar = ExampleEnum.ValueOne;
Console.WriteLine(ToStringEnums(enumVar));

我假设你想向用户显示你的枚举值,因此,你希望它们有一些友好的名称。

以下是我的建议:

使用枚举类型模式。虽然实施起来需要付出一些努力,但这真的很值得。

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

正如Yuriy所建议的,您可以使用__abc0属性。下面的扩展方法可以很容易地获得给定的枚举值的描述:

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声明扩展方法(就像可以为任何其他类型声明扩展方法一样)。我刚做了这个:

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());

希望这能有所帮助!

这个技巧的一个问题是描述属性不能被本地化。我很喜欢Sacha Barber的一项技术,他创建了自己版本的描述属性,该属性将从相应的资源管理器中获取值。

http://www.codeproject.com/kb/wpf/friendlyenums.aspx.

尽管本文讨论的是WPF开发人员在绑定到枚举时通常会遇到的问题,但您可以直接跳到他创建LocalizableDescriptionAttribute的部分。

一些伟大的解决方案已经发布。当我遇到这个问题时,我想采用两种方法:将枚举转换为描述,将与描述匹配的字符串转换为枚举。

我有两个变种,慢和快。从枚举到字符串和从字符串到枚举的转换。我的问题是我有这样的枚举,其中一些元素需要属性,而另一些则不需要。我不想把属性放在不需要它们的元素上。我目前有大约100个这样的总数:

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,
};

处理这个问题的第一种方法是缓慢的,并且是基于我在这里和网上看到的建议。它之所以缓慢,是因为我们在反思每一个转变:

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

这样做的问题是你每次都在做反射。我还没有衡量这样做对性能的影响,但这似乎令人震惊。更糟糕的是,我们正在重复计算这些昂贵的转换,而没有缓存它们。

相反,我们可以使用一个静态构造函数来用这些转换信息填充一些字典,然后在需要时查找这些信息。显然,静态类(扩展方法所必需的)可以有构造函数和字段:)

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,
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);
}

要求

  1. 枚举名称的字符数必须等于或大于所需的字符串。

  2. 你的枚举名称不应该在任何地方重复,以防字符串插入把事情搞砸。

为什么这是一个坏主意(几个原因)

  1. 由于需求,您的枚举名称变得很难看。

  2. 它依赖于您足够早地调用初始化方法。

  3. 不安全的指针

  4. 如果字符串的内部格式发生变化,例如长度字段被移动,你就完蛋了。

  5. 如果Enum.ToString()被更改为只返回一个副本,那么您就完蛋了。

  6. Raymond Chen将在他的下一个.NET周中抱怨您使用未记录的功能,以及CLR团队无法进行优化以减少50%的运行时间是您的错。

在阅读了有关此主题的许多参考资料(包括StackOverflow)之后,我发现并非所有解决方案都能正常工作。下面是我们试图解决这个问题。

基本上,我们从DescriptionAttribute(如果存在)中获取枚举的友好名称。
如果没有,我们使用regex来确定枚举名称中的单词并添加空格。

在下一个版本中,我们将使用另一个属性来标记是否可以/应该从可本地化的资源文件中获取友好名称。

下面是测试用例。如果您有其他未通过的测试用例,请报告。

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 = @"([A-Z]+(?![a-z])|\d+|[A-Z][a-z]+|(?![A-Z])[a-z]+)+";


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