C#获取枚举值

31

我有一个包含以下内容的枚举(例如):

  • UnitedKingdom,
  • UnitedStates,
  • France,
  • Portugal

在我的代码中,我使用 Country.UnitedKingdom ,但如果我将其分配给 string ,我希望该值为 UK

这可行吗?


可能是C#字符串枚举的重复问题。 - nawfal
另一篇帖子上的相关答案 - 我的枚举可以有友好的名称吗? - RBT
11个回答

53
你不能将枚举值直接赋给字符串。需要调用ToString(),将Country.UnitedKingdom转换为"UnitedKingdom"。
有两个可选方案:
  • 创建一个Dictionary<Country, string>
  • 使用switch语句
  • 使用反射为每个值添加属性,并加载属性
对于这三个方案的注释如下: Dictionary<Country,string>的示例代码:
using System;
using System.Collections.Generic;

enum Country
{
    UnitedKingdom, 
    UnitedStates,
    France,
    Portugal
}

class Test
{
    static readonly Dictionary<Country, string> CountryNames =
        new Dictionary<Country, string>
    {
        { Country.UnitedKingdom, "UK" },
        { Country.UnitedStates, "US" },
    };

    static string ConvertCountry(Country country) 
    {
        string name;
        return (CountryNames.TryGetValue(country, out name))
            ? name : country.ToString();
    }

    static void Main()
    {
        Console.WriteLine(ConvertCountry(Country.UnitedKingdom));
        Console.WriteLine(ConvertCountry(Country.UnitedStates));
        Console.WriteLine(ConvertCountry(Country.France));
    }
}

你可能希望将 ConvertCountry 的逻辑放入扩展方法中。例如:

// Put this in a non-nested static class
public static string ToBriefName(this Country country) 
{
    string name;
    return (CountryNames.TryGetValue(country, out name))
        ? name : country.ToString();
}

然后你可以写:
string x = Country.UnitedKingdom.ToBriefName();

正如评论中所提到的,使用默认字典比较器会涉及装箱,这是不理想的。对于一次性的情况,我可以接受,直到发现它成为瓶颈。如果我要为多个枚举编写此代码,我会编写一个可重用的类。

Switch语句

我同意yshuditelu的答案建议对于相对较少的情况使用switch语句。然而,由于每个case将是单个语句,我个人会为此情况改变我的编码风格,以保持代码紧凑但易读:

public static string ToBriefName(this Country country) 
{
    switch (country)
    {
        case Country.UnitedKingdom:  return "UK";
        case Country.UnitedStates:   return "US";
        default:                     return country.ToString();
    }
}

您可以添加更多的情况,而不会让它变得太庞大,而且很容易从枚举值转到返回值。

DescriptionAttribute

关于DescriptionAttribute代码可重用性的点Rado made是一个很好的点,但在这种情况下,我建议不要每次需要获取值时都使用反射。我可能会编写一个通用的静态类来保存查找表(可能是一个Dictionary,可能带有注释中提到的自定义比较器)。扩展方法无法在通用类中定义,因此您可能最终会得到类似于以下内容的东西:

public static class EnumExtensions
{
    public static string ToDescription<T>(this T value) where T : struct
    {
        return DescriptionLookup<T>.GetDescription(value);
    }

    private static class DescriptionLookup<T> where T : struct
    {
        static readonly Dictionary<T, string> Descriptions;

        static DescriptionLookup()
        {
            // Initialize Descriptions here, and probably check
            // that T is an enum
        }

        internal static string GetDescription(T value)
        {
            string description;
            return Descriptions.TryGetValue(value, out description)
                ? description : value.ToString();
        }
    }
}

10
只有在看到实际证据表明它是瓶颈时,我才会担心这个问题。一如既往地,使用最简单的代码,直到你有充分的理由去做其他事情。在我看来,“表现非常差”和“没有达到最优”之间存在很大的区别。 - Jon Skeet
1
@Ed Woodcock 整数实现IEquatable,并且由字典使用的EqualityComparer处理该特殊情况。 Ayende(Rhino Mocks的作者)曾经写过这个问题:http://ayende.com/Blog/archive/2009/02/21/dictionaryltenumtgt-puzzler.aspx - Igal Tabachnik
为什么在应用程序投入生产后,修复性能问题需要进行大规模重写?这只意味着改变扩展方法的实现。无论如何,我已经编辑了答案,因为您发表了评论-欢迎更多评论! - Jon Skeet
字典必须是公开可见的,才会成为一个问题 - 但很容易将其设置为类包含扩展方法的私有。 - Jon Skeet
1
我赞同Jon的观点 - 没有解释原因的负评对任何人都没有帮助。 - Scott Ivey
显示剩余5条评论

23

我喜欢在枚举类型上使用DescriptionAttribute。这样,您可以使用以下代码从枚举中获取描述。

enum MyCountryEnum
{    
    [Description("UK")]
    UnitedKingdom = 0,    

    [Description("US")]
    UnitedStates = 1,    

    [Description("FR")]
    France = 2,    

    [Description("PO")]
    Portugal = 3
}

public static string GetDescription(this Enum value)
{
    var type = value.GetType();

    var fi = type.GetField(value.ToString());

    var descriptions = fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];

    return descriptions.Length > 0 ? descriptions[0].Description : value.ToString();
}

public static SortedDictionary<string, T> GetBoundEnum<T>() where T : struct, IConvertible
{
    // validate.
    if (!typeof(T).IsEnum)
    {
        throw new ArgumentException("T must be an Enum type.");
    }

    var results = new SortedDictionary<string, T>();

    FieldInfo[] fieldInfos = typeof(T).GetFields();

    foreach (var fi in fieldInfos)
    {

        var value = (T)fi.GetValue(fi);
        var description = GetDescription((Enum)fi.GetValue(fi));

        if (!results.ContainsKey(description))
        {
            results.Add(description, value);
        }
    }
    return results;
}

然后获取我的绑定枚举列表,只需要调用

GetBoundEnum<MyCountryEnum>()

要获取单个枚举的描述,您只需使用扩展方法,如下所示:

只需使用扩展方法即可获取单个枚举的描述。

string whatever = MyCountryEnum.UnitedKingdom.GetDescription();

1
我一直收到 System.ArgumentException: Field 'value__' defined on type 'MyClass.EnumHelperTest+MyCountryEnum' is not a field on the target object which is of type 'System.Reflection.RtFieldInfo'. 的错误信息。初步研究表明,它不喜欢枚举不是一个实例? - Alex Angas
@AlexAngas 我已经修复了你的错误,并对Scott在这里提供的答案进行了一些其他小改进 https://dev59.com/PHNA5IYBdhLWcg3wVcJx#35053745 - DharmaTurtle
1
@AlexAngas 你的问题的解决方案在这里中讨论。简而言之,您需要向GetFields调用添加一些绑定标志,以排除具有特殊名称value__(参见.Net 4.6.1中的enumbuilder.cs的第416行)的非静态、私有后备字段。例如,此行有效:FieldInfo[] fieldInfos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static); - Tyson Williams

15

你可以创建一个扩展方法public static string ToShortString(this Country country)。在这个方法中,你可以像Jon建议的那样使用静态字典,或者你可以使用switch case语句。

例如:

public static class CountryExtensions
{
    public static string ToShortString( this Country target )
    {
        switch (target) {
            case Country.UnitedKingdom:
                return "UK";
            case Country.UnitedStates:
                return "US";
            case Country.France:
                return "FR";
            case Country.Portugal:
                return "PT";
            default:
                return "None";
        }
    }
}

1
只有4个值,我会选择使用switch语句。如果switch语句变得比字典实现更加繁琐,那么就重构为字典。 - tvanfosson
是的,我同意只使用4个switch case会是正确的选择。还有谢谢你添加代码,我还没有来得及做。 - Timothy Carter
+1 但请记住,列出的四个国家只是其中数量稍多的一部分 =] - Ed James

6

伪代码:

enum MyCountryEnum
{
    UnitedKingdom = 0,
    UnitedStates = 1,
    France = 2,
    Portugal = 3,
}

string[] shortCodes = new string[] {"UK", "US", "FR", "PO"};


MyCountryEnum enumValue = MyCountryEnum.UnitedKingdom;
string code = shortCodes[enumValue];

7
维护这个的开销令人担忧。保持两个不相互关联的数据结构同步总是一个警告信号。 - Rex M

3

我曾经不得不离开这个项目一段时间,现在回来后,我有了一瞬间的灵感。

与其使用枚举,我创建了一个新的类,如下所示:

public class Country
{
    public const string UnitedKingdom = "UK";
    public const string France = "F";
}

这样我就可以在我的代码中使用Country.UnitedKingdom,而值“UK”将被使用。
我只是提供这个答案作为另一种解决方案。
尼尔

3

还有一种可能性没有被提到,就是这样的:

public class Country
{
    public static readonly Country UnitedKingdom = new Country("UK");
    public static readonly Country UnitedStates = new Country("US");
    public static readonly Country France = new Country("FR");
    public static readonly Country Protugal = new Country("PT");

    private Country(string shortName)
    {
        ShortName = shortName;
    }

    public string ShortName { get; private set; }
}

从这个点开始,您可以添加更多的属性,但要注意不要向类中添加太多内容或静态成员,因为它会增加内存负担,可能不值得。

我认为很少有情况下这种策略是最佳方法,但当您想要将某些东西视为基本枚举时,这是一种需要注意的选项,可以尝试添加属性或属性。


1
如果您担心静态对象的内存使用情况,可以将它们制作为静态只读属性并使用弱引用。 - Matthew Whited
@Matthew Whited:你能提供一个在这种情况下如何使用弱引用的例子吗? - rohancragg

2

我试图提交对Scott Ivey的答案进行编辑,但被拒绝了,这里是另一个答案。我的相对较小的编辑:

1)我修复了Alex的错误:System.ArgumentException: Field 'value__' defined on type 'MyClass.EnumHelperTest+MyCountryEnum' is not a field on the target object which is of type 'System.Reflection.RtFieldInfo'。这里获取。

2)添加了一个return,所以你可以复制/粘贴它并且它会起作用。

3)将SortedDictionary更改为Dictionary,因为SortedDictionary总是按键排序,在这种情况下是字符串Description。没有理由按字母顺序排列说明。实际上,排序会破坏枚举的原始顺序。字典不保留枚举顺序,但至少不像SortedDictionary那样暗示顺序。

enum MyCountryEnum
{    
    [Description("UK")]
    UnitedKingdom = 0,    

    [Description("US")]
    UnitedStates = 1,    

    [Description("FR")]
    France = 2,    

    [Description("PO")]
    Portugal = 3
}

public static string GetDescription(this Enum value)
{
    var type = value.GetType();

    var fi = type.GetField(value.ToString());

    var descriptions = fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];

    return descriptions.Length > 0 ? descriptions[0].Description : value.ToString();
}

public static Dictionary<string, T> GetBoundEnum<T>() where T : struct, IConvertible
{
    // validate.
    if (!typeof(T).IsEnum)
    {
        throw new ArgumentException("T must be an Enum type.");
    }

    var results = new Dictionary<string, T>();

    FieldInfo[] fieldInfos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static);

    foreach (var fi in fieldInfos)
    {

        var value = (T)fi.GetValue(fi);
        var description = GetDescription((Enum)fi.GetValue(fi));

        if (!results.ContainsKey(description))
        {
            results.Add(description, value);
        }
    }
    return results;
}

2

只需使用DescriptionAttribute

如果您仅需要获取枚举值的字符串表示形式,则无需创建字典。请参阅此示例

[编辑]哦...忘记提到它比字典更可重用,因为您只需要一个常见的util类来帮助获取描述,然后您所需要做的就是在添加枚举值或创建具有相同要求的新枚举时添加DescriptionAttribute。在字典/ switch解决方案中,它更难以维护,并且一旦您拥有许多枚举类型,它会变得混乱。


实际上,那是我提到的另一种选择。但是,它需要更多的代码并且使用了反射(除非我真的需要它,否则通常会避免使用)。当然,DescriptionAttribute没有任何神奇之处 - 您可以创建自己的属性并使用它代替。 - Jon Skeet

1
每当我看到一个枚举时,我就觉得代码应该被重构。为什么不创建一个Country类并添加一些方法来解决你试图避免的障碍呢?给枚举分配值甚至更加糟糕。
为什么会有人踩这篇文章呢?我认为使用多态方法比使用枚举是被广泛接受的。当你可以使用ValueObject设计时,没有理由使用枚举。
这里有一篇关于这个主题的好博客文章: http://devpinoy.org/blogs/cruizer/archive/2007/09/12/enums-are-evil.aspx

我认为几乎所有使用枚举的情况下,他们都应该仔细检查自己的代码,看看对象是否是更好的选择。 - Dan
这绝对是最干净的做事方式。我很惊讶你没有得到更多的赞。 - Kramii

0
var codes = new Dictionary<Country, string>() 
        { { Country.UnitedKingdom, "UK" },
        { Country.UnitedStates, "US" },
        { Country.France, "FR" } };
Console.WriteLine(codes[Country.UnitedStates]);

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