将枚举转换为字符串的最佳实践方法是什么?

9

I have enum like this:

public enum ObectTypes
{
    TypeOne,
    TypeTwo,
    TypeThree,
    ...
    TypeTwenty
 }

然后我需要将这个枚举转换为字符串。现在我是这样做的:
public string ConvertToCustomTypeName(ObjectTypes typeObj)
{
    string result = string.Empty;
    switch (typeObj)
    {
        case ObjectTypes.TypeOne: result = "This is type T123"; break;
        case ObjectTypes.TypeTwo: result = "Oh man! This is type T234"; break;
        ...
        case ObjectTypes.TypeTwenty: result = "This is type last"; break;
    }

    return result;
}

我相信有更好的方法来解决这个问题,我正在寻找一些好的实践方案。

编辑:结果字符串中没有固定模式。

提前感谢您。


另外,就我所知,你根本不需要结果对象,只需返回这些字符串即可,也可以摆脱break语句。 - Chris Marisic
@Chris Marisic。 是的,你是对的,但那个变量只是为了更好的阅读体验 :-) - Dariusz
10个回答

19
我使用来自System.ComponentModel的[Description]属性。
示例:
public enum RoleType
{
    [Description("Allows access to public information")] Guest = 0,
    [Description("Allows access to the blog")] BlogReader = 4,
}

然后我从它中读取

public static string ReadDescription<T>(T enumMember)
{
    var type = typeof (T);

    var fi = type.GetField(enumMember.ToString());
    var attributes = (DescriptionAttribute[]) 
            fi.GetCustomAttributes(typeof (DescriptionAttribute), false);
    return attributes.Length > 0 ? 
        attributes[0].Description : 
        enumMember.ToString();
}

然后使用

ReadDescription(RoleType.Guest);

注意:此解决方案假定应用程序只有一个语言环境,因为没有特别要求处理多个语言环境。如果您需要处理多个语言环境,我建议使用DescriptionAttribute或类似方法将键存储到支持多语言的资源文件中。虽然您可以直接在.resx文件中存储枚举成员,但这会创建最紧密的耦合。我认为您不应该将应用程序的内部工作(枚举成员名称)与存在于国际化目的的键值耦合在一起。


3
这太慢了 - 根据使用情况可能重要,也可能不重要。 - Reed Copsey
6
过早地进行优化会招来麻烦。如果性能是一个重要考虑因素,你可以实现缓存,只需读取每个枚举成员一次即可。我使用这种方法来填充下拉列表,因此在枚举遍历之后,将我的Code/Value对枚举的单例副本缓存到我的模型中。如果我担心反射成本,我会实际上在ReadDescription方法内部使用字典或哈希表等缓存它。 - Chris Marisic
1
这种方法也不具备文化意识。如果您只需要发布一种语言,那么这是可以的,但是一旦您需要另一种语言或客户要求定制,您就必须保留编译后的dll的另一个副本。 - slugster
@slugster 如果我需要进行本地化,我会使用枚举名称或枚举描述在与文化匹配的resx文件或类似文件中查找。我仍然可能使用描述属性或类似属性,我不认为我的枚举值应该与resx键完全耦合。 - Chris Marisic
@slugster 它刚刚被点赞了,这让我重新关注它。 - Chris Marisic
显示剩余5条评论

9
如果您需要自定义字符串,最好的选择是创建一个Dictionary<ObjectTypes,string>,然后进行字典查找。
如果您满意默认的ToString()功能,请使用typeObj.ToString(); 对于字典方法,您可以执行以下操作:
private static Dictionary<ObjectTypes, string> enumLookup;

static MyClass()
{
    enumLookup = new Dictionary<ObjectTypes, string>();
    enumLookup.Add(ObjectTypes.TypeOne, "This is type T123");
    enumLookup.Add(ObjectTypes.TypeTwo, "This is type T234");
    // enumLookup.Add...

}

你的方法变为:
public string ConvertToCustomTypeName(ObjectTypes typeObj)
{
     // Shouldn't need TryGetValue, unless you're expecting people to mess  with your enum values...
     return enumLookup[typeObj];
}

我觉得有必要指出,对于紧凑的枚举(没有大的值间隔),List<string>会更加高效。 - Simon Buchan
@Simon:是的,可能性是存在的 - 尽管这更加灵活,适用于枚举中的任何值类型,具有任意的枚举值... - Reed Copsey
@Simon:不过,只有当枚举值从0开始或接近0时才是真实的... 如果您开始获得无法轻松转换为列表索引的“值”,则哈希速度将更快。 - Reed Copsey

4

使用建议的资源方法:

string GetName(Enum e) {
     return Properties.Resources.ResourcesManager.GetString("_enum_"+e.GetType().ToString().Replace('.','_'));
}

错误处理略微有些...


2

我认为最简单的方法是创建一个函数,将枚举值和首选短名称之间进行映射,然后再创建另一个函数来生成完整的消息。

internal static string MapToName(ObjectTypes value) {
  switch (value) { 
    case ObjectTypes.TypeOne: return "T123";
    case ObjectTypes.TypeTwo: return "T234";
    ...
  }
}

public string ConvertToCustomTypeName(ObjectTypes value) {
  return String.Format("This is type {0}", MapToName(value));
}

很明显他想将其转换为任意字符串,而不是枚举字段的名称。 - Matti Virkkunen
前缀不是常数,后缀也不是常数,因此我无法预测字符串的起始位置。 - Dariusz
@dario,能否给我们提供更多的例子?仅依据这个有限的样本很难回答这个问题。 - JaredPar
当然,以下是一些示例: "Cramshaft T124","低信号S","高信号","过度增压1E","检查引擎!","致命错误","未知"等。 - Dariusz

1

如果您只想使用枚举名称(例如TypeOne),则可以直接在枚举本身上调用ToString()函数。

typeObj.ToString()

如果你想根据类型创建自定义字符串,有几种不同的选择。你现在使用的switch语句还可以,但是如果你有大量的枚举值需要维护,那么代码会变得混乱。或者你可以设置一个基于字典的方案,使用枚举类型作为键,字符串作为值。

public enum ObectTypes
{
   One,
   Two
}

Dictionary<ObectTypes, String> myDic = new Dictionary<ObectTypes, string>();
myDic.Add( ObectTypes.One, "Something here for One" );
myDic.Add( ObectTypes.Two, "Something here for Two" );

1

我曾经在枚举字段上使用了自定义属性,该属性带有一个字符串参数,然后编写了一个函数,在给定枚举值时提取字符串。


哦,我不知道枚举成员是可归属的 :)。 - Simon Buchan

1

我简直不敢相信这个...为什么没有人建议使用资源文件呢?

在编译代码中将枚举值映射到字符串是一个快速的技巧,但从长远来看,这是一种不好的做法,会使重构代码变得困难。如果您从枚举中添加(或减去)一个值,那该怎么办?如果您使用来自资源文件的字符串,您只需要添加(或删除)一个条目。


1
我认为很少有C#程序员使用资源文件。似乎只有C++/win32/MFC编码者才看到了资源文件的光芒。 - Justin
@Justin - 我同意,使用它们的人不够多,可能是从VB6代码编写的旧时习惯。我认为随着越来越多的人意识到正确地将UI与业务逻辑分离(即MVVM模式),人们将开始更多地使用资源文件,特别是因为资源文件可以作为静态绑定的一部分被利用。 - slugster
我不同意这是一种不好的做法,我已经使用这种方法多年了,没有遇到任何问题。唯一不合理的情况是如果列表必须是动态的并且经常更改,在这种情况下,无论是否使用描述,枚举都没有意义。 - Chris Marisic
@Chris,你有权不同意我的看法,而且我注意到你的答案得票最高。只要您的应用程序不是多语言的(即使英语也有变体),您是正确的。即使您的应用程序仅针对特定文化(例如en-US),将文本放入资源文件的额外抽象也是一个很好的实践。在 n-tier 应用程序中,枚举通常在低级公共程序集中声明,但文本是 UI 事物,属于 UI 相关的程序集。你的回答肯定是可以“work”的,但它并不是我试图指出的理想答案。 - slugster
我不同意你的解决方案比我的更理想,我认为除非你需要额外的全球化抽象,否则它实际上更有问题。因此,将一个完全可行的解决方案说成是不良实践不仅仅是一个用词不当,而且是一个错误的陈述。 - Chris Marisic

1

我会将这些值放入数据库中。角色通常需要放在数据库中,原因如下:

  1. 可能需要对角色运行报告,因此需要显示描述。
  2. 可以配置角色定义而无需部署新代码。
  3. 应该使用关键约束来强制执行角色。

如果正确完成,从数据库获取角色时性能不应成为问题。此外,由于角色定义表很少更改,因此非常适合缓存。

如果发现自己不得不绕过枚举才能使其正常工作,也许就不应该使用枚举。如果放置得当,一切都应该在代码中流畅地进行,几乎不需要任何黑客技巧。当然,枚举也有其用处;例如,一个相当静态的互斥状态。


0

你已经在使用的模式并不是一个坏模式。几乎每个其他选项都有自己的问题。如果你正在使用 F# 进行此类型的编程,可以尝试以下代码:

type ObjectTypes =
| TypeOne
| TypeTwo
| TypeThree
...
| TypeTwenty
with override this.ToString() =
    match this with
    | TypeOne -> "This is type T123"
    | TypeTwo -> "Oh man! This is type T234"
    ...
    | TypeTwenty -> "This is type last"
    | _ -> "This is any other type that wasn't explicitly specified"

当然,使用F#模式匹配,您比使用简单的C# switch语句拥有更多的控制权。

0

1
我并不完全同意这个观点,如果枚举类型确实具有代码值和显示值,那么使用过度的方法将显得多余。我经常使用枚举类型来填充下拉列表。 - Chris Marisic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接