在C#中,将枚举值与字符串关联

609

我知道以下是不可能的,因为枚举类型必须是int。

enum GroupTypes
{
    TheGroup = "OEM",
    TheOtherGroup = "CMB"
}

我从数据库中获取了一个包含难以理解的代码(OEMCMB)的字段。 我想将此字段转换为枚举或其他易于理解的形式。 因为如果目标是可读性,解决方案应该是简洁的。

我还有哪些选项?


1
可能是Enum ToString的重复问题。 - nawfal
23
我不确定为什么大多数答案不直接使用“const string”,而是制作自定义类。 - CTS_AE
2
你可能无法使用字符串,但是你可以很好地使用字符。如果你可以使用单个字母值,那就是一个选项。 - T. Sar
1
真的很困惑为什么CTS_AE提出的解决方案甚至没有进入前三名答案。 - Sinjai
5
@Sau001 / CTS_AE - 我想把这个放在这里,因为这些评论在顶部,需要滚动一段时间才能找到答复。请参阅Pharap在此回答中的评论:https://dev59.com/B3RB5IYBdhLWcg3wa2q2#5674697. 静态类不能用作方法参数类型,因此您无法强制使用其中一个预定义的常量字符串来调用方法。 - Robin Zimmerman
显示剩余2条评论
39个回答

661
我更喜欢在类中使用属性而不是方法,因为它们看起来更像枚举。下面是Logger的一个示例:
public class LogCategory
{
    private LogCategory(string value) { Value = value; }

    public string Value { get; private set; }

    public static LogCategory Trace   { get { return new LogCategory("Trace"); } }
    public static LogCategory Debug   { get { return new LogCategory("Debug"); } }
    public static LogCategory Info    { get { return new LogCategory("Info"); } }
    public static LogCategory Warning { get { return new LogCategory("Warning"); } }
    public static LogCategory Error   { get { return new LogCategory("Error"); } }

    public override string ToString()
    {
        return Value;
    }
}

类型安全的字符串值作为参数传入:

public static void Write(string message, LogCategory logCategory)
{
    var log = new LogEntry { Message = message };
    Logger.Write(log, logCategory.Value);
}

使用方法:

Logger.Write("This is almost like an enum.", LogCategory.Info);

47
为了自己的使用,我对这个概念进行了扩展,重写了ToString方法以返回Value。然后提供了隐式转换操作符来实现字符串与该类型之间的互相转换。其中包括从该类型到字符串的转换:public static implicit operator String(LogCategory category) { return Value; } - Zarepheth
17
这个短语可以用在switch语句中吗? - David
6
明智的决定。我建议将Value的赋值方法设为私有的,然而。 - bohdan_trotsenko
4
缺点是函数参数中无法使用默认值: public string GetLogs(LogCategory cat = LogCategory.Trace) 会返回错误信息"默认参数值必须是编译时常量"。 - Miro J.
18
现在有了C# 6,你可以将它写得更加简洁:public static string Trace => "Trace"; - JonathanPeel
显示剩余27条评论

277
你也可以使用扩展模型:
public enum MyEnum
{
    [Description("String 1")]
    V1= 1,
    [Description("String 2")]
    V2= 2
} 

你的扩展类

public static class MyEnumExtensions
{
    public static string ToDescriptionString(this MyEnum val)
    {
        DescriptionAttribute[] attributes = (DescriptionAttribute[])val
           .GetType()
           .GetField(val.ToString())
           .GetCustomAttributes(typeof(DescriptionAttribute), false);
        return attributes.Length > 0 ? attributes[0].Description : string.Empty;
    }
} 

使用方法:

MyEnum myLocal = MyEnum.V1;
print(myLocal.ToDescriptionString());

3
请参考 https://dev59.com/n2855IYBdhLWcg3wZzXf,了解另一种通过描述属性从字符串转换为枚举的扩展方法。 - Dave
46
我无法不想到,每次想显示文本时都要反射枚举从性能角度来看似乎有些痛苦! - Liath
8
@Liath - 使用 .ToString() 已经使用了反射,因此您不会因使用这种方法而丢失任何内容,并且还能提高可读性。 - James King
7
要使其通用,使用 public static string ToDescriptionString(this Enum ... 即可,无需显式键入为 MyEnum - LeeCambl
2
这需要使用 System.ComponentModel; - Cody G
显示剩余4条评论

177

使用一个带有常量的静态类如何?

static class GroupTypes
{
  public const string TheGroup = "OEM";
  public const string TheOtherGroup = "CMB";
}

void DoSomething(string groupType)
{
  if(groupType == GroupTypes.TheGroup)
  {
    // Be nice
  }  
  else if (groupType == GroupTypes.TheOtherGroup)
  {
    // Continue to be nice
  }
  else
  {
    // unexpected, throw exception?
  }
}

11
同意。我很难看出更复杂的解决方案背后的目的,除了可能是为了能够切换生成的“枚举类型”。 - fakeleft
1
@fakeleft 你不能在泛型中使用静态类类型,可能会有其他限制,我认为这就是为什么人们更喜欢“更复杂”的解决方案。 - eselk
3
为了使其正常工作,这些常量需要是内部的或公共的。 - arviman
81
静态类型不能用作参数。 - Pedro Moreira
10
正如 @PedroMoreira 指出的那样,你不能将 GroupTypes 作为参数类型传递,因为它是一个静态类。这就是 Even Mien 的回答要解决的问题。在这种情况下,你需要使用 void DoSomething(string groupType),这意味着 groupType 可以具有任何字符串值,甚至是你没有预期到的值,这意味着你必须准备好处理那些无效的类型,并决定如何处理它们(例如通过抛出异常)。Even Mien 的答案通过将有效输入的数量限制为由 LogCategory 类定义的选项来解决这个问题。 - Pharap
显示剩余6条评论

60

我采用了之前回答中提到的结构,但取消了任何复杂性。 对我来说,这最像创建字符串枚举。 它的使用方式与枚举的使用方式相同。

    struct ViewTypes
    {
        public const string View1 = "Whatever string you like";
        public const string View2 = "another string";
    }

使用示例:

   switch( some_string_variable )
   {
      case ViewTypes.View1: /* do something */ break;
      case ViewTypes.View2: /* do something else */ break;
   }

3
在所有给出的答案中,我认为这是最好的解决方案。几乎所有的答案都试图硬塞一个解决方案,但它们增加了比应该更多的复杂性。有时候,使用不同的解决方案或数据结构比强制让某些东西按照你想要的方式工作更好。我个人使用这样的结构体而不是枚举,这使得我的代码更易读和可维护。 - Halcyon
简单而有效! - Canada Wan

47

尝试将常量添加到静态类中。这样你不会得到一个类型,但是你会拥有可读性强、组织良好的常量:

public static class GroupTypes {

    public const string TheGroup = "OEM";
    public const string TheOtherGroup = "CMB";

}

4
从代码返回到描述性名称比较困难。你必须对所有const字段使用反射以查找匹配项。 - andleer
1
@andleer,我不明白你的疑虑。这是我使用的解决方案。 - VSO
是的,这正是我想要的。这也是我看到的最简洁/优雅的解决方案,就像我定义一个具有整数值的枚举一样 - 只不过这里使用字符串值。100% 完美。 - Chad
8
问题在于,它不能像枚举一样工作,因为我们没有一个有限值列表的单独类型。期望这些内容的函数可能会与自由格式字符串一起使用,这很容易出错。 - Juan Martinez

44

实际上你可以很容易地完成它。使用以下代码。

enum GroupTypes
{
   OEM,
   CMB
};

如果你想获取每个枚举元素的字符串值,只需使用以下代码行。

String oemString = Enum.GetName(typeof(GroupTypes), GroupTypes.OEM);

我过去已经成功地使用了这种方法,也使用常量类来保存字符串常量,两种方法都行得不错。但我更倾向于使用这种方法。


我所知道的唯一限制是,我相信它使用反射来找出字符串。因此,如果我只是想要一个解决方案来跟踪常量字符串,那么我通常会使用一个类来存储大部分常量字符串。但是,如果我有一个情况,其中枚举是正确的解决方案(无论是否获取有关我的枚举元素的描述性字符串),那么与其在某个地方浮动额外的字符串来管理,我只需使用所描述的枚举值。 - Arthur C
+1 这是最好和最简单的答案,而且在这里有高票数来证明它。唯一需要使用扩展模型的时候是当你需要在文本中添加空格(更多细节请参见此处)。 - SharpC
33
不,这只是获取枚举值的名称,而不是将字符串分配给枚举值。 OP 的目标是拥有一个与枚举值不同的字符串,例如:TheGroup = "OEM",TheOtherGroup = "CMB"。 - Tim Autin
3
我同意@Tim的评论,这不是原帖作者想要做的。如果你想知道这样做的用例,可以考虑一种情况,即设备以字符串作为命令,但还需要一个“可读性强”的命令版本。我曾需要将“Update Firmware”与命令“UPDATEFW”相关联。 - JYelton
除此之外,这种方法对于不是有效标识符的字符串,如“123”或some string. 123是行不通的。 - phuclv
显示剩余2条评论

35

您可以为枚举中的项目添加属性,然后使用反射从属性中获取值。

您需要使用“field”说明符来应用属性,如下所示:

enum GroupTypes
{
    [field:Description("OEM")]
    TheGroup,

    [field:Description("CMB")]
    TheOtherGroup
}

然后,您需要反射枚举类型(在此示例中为GroupTypes)的静态字段,并使用反射获取您正在查找的值的DescriptionAttribute

public static DescriptionAttribute GetEnumDescriptionAttribute<T>(
    this T value) where T : struct
{
    // The type of the enum, it will be reused.
    Type type = typeof(T);

    // If T is not an enum, get out.
    if (!type.IsEnum) 
        throw new InvalidOperationException(
            "The type parameter T must be an enum type.");

    // If the value isn't defined throw an exception.
    if (!Enum.IsDefined(type, value))
        throw new InvalidEnumArgumentException(
            "value", Convert.ToInt32(value), type);

    // Get the static field for the value.
    FieldInfo fi = type.GetField(value.ToString(), 
        BindingFlags.Static | BindingFlags.Public);

    // Get the description attribute, if there is one.
    return fi.GetCustomAttributes(typeof(DescriptionAttribute), true).
        Cast<DescriptionAttribute>().SingleOrDefault();
}

我选择返回DescriptionAttribute本身,以便您可以确定是否应用了该属性。


虽然我会记住这个方法以应对更复杂的情况,但对于我在原帖中提到的那种复杂度水平的情况来说,它还是相当复杂的。 - Boris Callens

16

使用一个类。

编辑:更好的示例

class StarshipType
{
    private string _Name;
    private static List<StarshipType> _StarshipTypes = new List<StarshipType>();

    public static readonly StarshipType Ultralight = new StarshipType("Ultralight");
    public static readonly StarshipType Light = new StarshipType("Light");
    public static readonly StarshipType Mediumweight = new StarshipType("Mediumweight");
    public static readonly StarshipType Heavy = new StarshipType("Heavy");
    public static readonly StarshipType Superheavy = new StarshipType("Superheavy");

    public string Name
    {
        get { return _Name; }
        private set { _Name = value; }
    }

    public static IList<StarshipType> StarshipTypes
    {
        get { return _StarshipTypes; }
    }

    private StarshipType(string name, int systemRatio)
    {
        Name = name;
        _StarshipTypes.Add(this);
    }

    public static StarshipType Parse(string toParse)
    {
        foreach (StarshipType s in StarshipTypes)
        {
            if (toParse == s.Name)
                return s;
        }
        throw new FormatException("Could not parse string.");
    }
}

1
从代码返回到描述性名称很困难。您必须在所有const字段上使用反射来搜索匹配项。 - andleer
1
我明白你的意思。稍后我会上传一个实际可用的版本,但我承认它相当沉重。 - C. Ross
我的版本基于C.Ross的解决方案 https://dev59.com/B3RB5IYBdhLWcg3wa2q2#48441114 - Roman M

15
创建第二个枚举类型,用于你的数据库,包含以下内容:
enum DBGroupTypes
{
    OEM = 0,
    CMB = 1
}

现在,您可以使用Enum.Parse从字符串“OEM”和“CMB”中检索正确的DBGroupTypes值。然后,您可以将它们转换为int,并从想要在模型中进一步使用的正确枚举中检索正确的值。


这似乎是流程中的一个额外步骤,为什么不使用一个类来处理所有事情呢? - C. Ross
12
与使用属性和反射相反? - Dave Van den Eynde

14

另一种处理这个问题的方法是创建一个枚举和一个字符串数组,将枚举值映射到字符串列表中:

public enum GroupTypes
{
    TheGroup  = 0,
    TheOtherGroup 
}

string[] GroupTypesStr = {
    "OEM",
    "CMB"
};

你可以像这样使用它:

Log.Write(GroupTypesStr[(int)GroupTypes.TheOtherGroup]);

它会提示招商银行

优点:

  1. 代码简单干净。
  2. 高性能(与使用类的方法相比特别如此)。

缺点:

  1. 在编辑列表时容易弄乱,但对于短列表来说还好。

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