如何定义一个带有字符串值的枚举?

165
我正在尝试定义一个枚举Enum,并添加在CSV或类似文件中使用的有效通用分隔符。然后,我将把它绑定到一个ComboBox作为数据源,这样每当我添加或删除枚举定义时,我就不需要在组合框中更改任何内容。
问题是如何定义具有字符串表示的枚举,例如: public enum SeparatorChars{Comma = ",", Tab = "\t", Space = " "}

2
FYI,有一个专门处理这个问题的库:https://www.nuget.org/packages/EnumStringValues/,它实现了Amit Rai Sharma的解决方案。 - Brondahl
21个回答

146

你不能使用枚举类型 - 枚举值必须是整数。你可以使用属性将每个枚举值与字符串关联起来,或者在这种情况下,如果每个分隔符都是单个字符,你可以直接使用 char 类型的值:

enum Separator
{
    Comma = ',',
    Tab = '\t',
    Space = ' '
}

(编辑:为了澄清,您不能将char作为枚举的基础类型,但您可以使用char常量来分配与每个枚举值对应的整数值。上述枚举的基础类型是int。)
如果需要,可以使用以下扩展方法:
public string ToSeparatorString(this Separator separator)
{
    // TODO: validation
    return ((char) separator).ToString();
}

14
您可以按照我的回答使用char字面值来表示value。我已经测试过了 :) - Jon Skeet
2
@JonSkeet 哦,你是对的,它们当然可以转换为int。不用在意。 - dowhilefor
2
@ShaunLuttin:枚举只是“命名数字”-因此,字符串枚举实际上与该模型不符。 - Jon Skeet
1
@ShaunLuttin:嗯,它们是非常不同的语言。我认为你不应该期望从C#中的所有内容都能在TypeScript中找到,反之亦然。 - Jon Skeet
1
@ShaunLuttin:说实话,我会放手不管。这不仅仅是C#的问题——整个.NET都期望枚举基于整数。因此,您将要求更改CLR等内容。该功能并未设计为基于底层字符串值,而TypeScript在CLR设计15年后拥有它们并不能真正改变这一事实。 - Jon Skeet
显示剩余4条评论

138
你可以做到,但需要一些努力。
定义一个属性类,其中包含枚举的字符串值。
定义一个扩展方法,该方法将从属性返回值。例如,`GetStringValue(this Enum value)` 将返回属性值。
然后,您可以像这样定义枚举:
```csharp public enum Test : int { [StringValue("a")] Foo = 1, [StringValue("b")] Something = 2 } ```
要从属性获取值,使用 `Test.Foo.GetStringValue();`
参考: C#中的带有字符串值的枚举

13
我知道这句话有些老,但它显然很独特,可以让你在代码中使用枚举并在数据库中使用字符串值。非常惊人。 - A_kat
2
又是一个晚评论,但这确实是一个非常棒的解决方案。 - Alan
2
请记住反射会对性能造成影响,如果这在你的情况下是相关的。 - Matt Jenkins
2
绝对惊人的答案!喜欢简单的实现。 - Aweda
3
FYI,我在多年前将这个确切的解决方案实现为一个Nuget包,所以你可以JustUseIt而不用担心实现支持属性和访问器方法(此外,我还添加了一些性能优化和额外的功能):DNuget包:https://www.nuget.org/packages/EnumStringValues/ - Brondahl

103
据我所知,您不能将字符串值分配给枚举。您可以创建一个包含字符串常量的类来实现相同的功能。
public static class SeparatorChars
{
    public static String Comma { get { return ",";} } 
    public static String Tab { get { return "\t,";} } 
    public static String Space { get { return " ";} } 
}

20
与其他方法相比,这种方法的缺点是您无法在不进行额外/特殊操作的情况下枚举它们。 - caesay
3
由于separator现在是一个字符串(可以是任何内容),而不是一个有受限有效值的Separator类型,因此这不能在编译时强制执行特定的值。 - ChickenFeet

76

对于一个简单的字符串(或任何其他类型)枚举:

public static class MyEnumClass
{
    public const string 
        MyValue1 = "My value 1",
        MyValue2 = "My value 2";
}

用法: string MyValue = MyEnumClass.MyValue1;


11
虽然这不是枚举类型,但我认为这可能是用户试图解决问题的最佳方案。有时候,最简单的解决方案就是最好的。 - Zesty
这对我来说就是这样了。 - mishal153

57

也许现在已经太晚了,但还是要试一下。

我们可以使用EnumMember属性来管理枚举值。

public enum UnitOfMeasure
{
    [EnumMember(Value = "KM")]
    Kilometer,
    [EnumMember(Value = "MI")]
    Miles
}

这样,UnitOfMeasure的结果值将为KM或MI。这也可以在Andrew Whitaker的答案中看到。


1
最好和最简单的答案。完美运作。 - SpeedOfSpin
4
除了序列化(Serialization)这个枚举使用的一小部分之外,它并不起作用。我想知道有多少赞是因为 Stack Overflow 在 6 分钟后锁定了投票。 - m12lrpv

35
你不能使用枚举做到这一点,但你可以这样做:
public static class SeparatorChars
{
    public static string Comma = ",";

    public static string Tab = "\t";

    public static string Space = " ";
}

1
+1 虽然我认为这是正确的解决方案,但我会更改类的名称或将类型更改为 chars,以保持一致性。 - dowhilefor
谢谢,你能告诉我在这种情况下等价于 comboBox.DataSource = Enum.GetValues(typeof(myEnum)); 的是什么吗? - Dumbo
1
@Sean87:如果你想要那个的话,我会采纳JonSkeets的答案。 - Fischermaen
我认为这几乎是正确的答案,因为它不能在switch-case块内使用。这些字段应该是const类型。但如果你想要Enum.GetValues(typeof(myEnum)),那也无可厚非。 - André Santaló
7
我会使用 const 而不是 static。常量和静态变量一样是只读的,在构造函数中无法赋值(除非是只读字段)。 - Olivier Jacot-Descombes
这不太好。你应该使用静态getter或只读属性而不是公共成员!!! - Mahdi

22

可以创建一个模拟枚举行为但是使用 string 而不是 int 的类,方法如下...

public class GrainType
{
    private string _typeKeyWord;

    private GrainType(string typeKeyWord)
    {
        _typeKeyWord = typeKeyWord;
    }

    public override string ToString()
    {
        return _typeKeyWord;
    }

    public static GrainType Wheat = new GrainType("GT_WHEAT");
    public static GrainType Corn = new GrainType("GT_CORN");
    public static GrainType Rice = new GrainType("GT_RICE");
    public static GrainType Barley = new GrainType("GT_BARLEY");

}

用法...

GrainType myGrain = GrainType.Wheat;

PrintGrainKeyword(myGrain);

那么...

public void PrintGrainKeyword(GrainType grain) 
{
    Console.Writeline("My Grain code is " + grain.ToString());   // Displays "My Grain code is GT_WHEAT"
}

唯一的问题是你不能这样做 GrainType myGrain = "GT_CORN",例如。 - komodosp
1
你可以通过重载运算符来实现。 - Jif

14

虽然回答有点晚了,但希望对未来的某个人有所帮助。我发现在这种问题中使用结构体更容易。

以下示例是从微软代码中复制粘贴的部分:

namespace System.IdentityModel.Tokens.Jwt
{
    //
    // Summary:
    //     List of registered claims from different sources http://tools.ietf.org/html/rfc7519#section-4
    //     http://openid.net/specs/openid-connect-core-1_0.html#IDToken
    public struct JwtRegisteredClaimNames
    {
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Actort = "actort";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Typ = "typ";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Sub = "sub";
        //
        // Summary:
        //     http://openid.net/specs/openid-connect-frontchannel-1_0.html#OPLogout
        public const string Sid = "sid";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Prn = "prn";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Nbf = "nbf";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Nonce = "nonce";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string NameId = "nameid";

    }
}

4
请问您能否解释一下为什么使用这种方法比使用类更好? - Gerardo Grignoli
2
@GerardoGrignoli 我不确定为什么微软在这种情况下使用结构体而不是类。我甚至没有尝试去了解,因为这对我来说完全有效。也许你可以在这里在Stack上提问。 - suchoss
1
结构体应该只用于表示单个值,小于16字节,是不可变的,并且不必频繁进行取消装箱。这个答案不符合条件,应该使用类。来源: https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct - JMD
  1. 这段代码是从.NET复制粘贴而来的。
  2. 它代表单个值,它是不可变的,并且没有被拆箱。
  3. 它可能不在16B以下,但考虑到它的预期使用方式,我认为这不是一个问题。
- suchoss
2
我明白这段代码是从旧示例中复制粘贴的。我只是提供新的指南,以便人们不会认为这些信息仍然是正确的。请查看我提供的链接,以获取有关为什么此答案不符合当前指南的更多信息。 - JMD

12
你无法这样做,因为枚举只能基于原始数值类型。 你可以尝试使用一个Dictionary代替:
Dictionary<String, char> separators = new Dictionary<string, char>
{
    {"Comma", ','}, 
    {"Tab",  '\t'}, 
    {"Space", ' '},
};

或者,您可以使用一个Dictionary<Separator, char>Dictionary<Separator, string>,其中Separator是普通的枚举类型:
enum Separator
{
    Comma,
    Tab,
    Space
}

这将比直接处理字符串更加方便。


10

如果您正在寻找更一般的答案,想让您的代码看起来像一个enum,则可以扩展静态类概念。

以下方法适用于当您尚未确定要使用的enum名称,并且enum值enam名称string表示时;使用nameof()可以使重构变得更简单。

public static class Colours
{
    public static string Red => nameof(Red);
    public static string Green => nameof(Green);
    public static string Blue => nameof(Blue);
}

这实现了具有字符串值的枚举类型的意图(例如以下伪代码):
public enum Colours
{
    "Red",
    "Green",
    "Blue"
}

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