【问题标题】:C# Getting Enum valuesC# 获取枚举值
【发布时间】:2010-11-03 17:34:27
【问题描述】:

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

  • 英国,
  • 美国,
  • 法国,
  • 葡萄牙

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

这可能吗?

【问题讨论】:

标签: c# .net string enums


【解决方案1】:

我更喜欢在我的枚举上使用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();

【讨论】:

  • 我不断收到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'. 初步研究表明它不喜欢枚举不是实例?
  • @AlexAngas 我修复了您的错误,并在此处为 Scott 的回答提供了一些其他小的改进stackoverflow.com/a/35053745/625919
  • @AlexAngas 讨论了您的问题的解决方案here‌。简而言之,您需要在GetFields 调用中添加一些绑定标志,以排除具有特殊名称value__ 的非静态私有支持字段(参见enumbuilder.cs in .Net 4.6.1 的第416 行)。例如,此行有效:FieldInfo[] fieldInfos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static);
【解决方案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'. Got it from here

2) 添加了return,这样您就可以实际复制/粘贴它并且它会起作用。

3) 将 SortedDictionary 更改为 Dictionary,因为 SortedDictionary 始终按键排序,在本例中为字符串 Description。没有理由按字母顺序排列描述。事实上,对它进行排序会破坏枚举的原始顺序。 Dictionary 也不保留枚举顺序,但至少它不像 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;
}

【讨论】:

    【解决方案3】:

    您不能将枚举值分配给开头的字符串。您必须致电ToString(),这会将Country.UnitedKingdom 转换为“UnitedKingdom”。

    两个选项建议自己:

    • 创建Dictionary&lt;Country, string&gt;
    • switch 语句
    • 用属性装饰每个值,并用反射加载它

    对他们每个人的评论......

    Dictionary&lt;Country,string&gt; 的示例代码

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

    如 cmets 中所述,默认的字典比较器将涉及装箱,这是不理想的。对于一次性的,我会忍受它,直到我发现它是一个瓶颈。如果我对多个枚举执行此操作,我会编写一个可重用的类。

    切换语句

    我同意yshuditelu's answer 建议在相对较少的情况下使用switch 语句。但是,由于每个案例都将是一个单独的语句,因此我会针对这种情况亲自更改我的编码风格,以保持代码紧凑但可读:

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

    您可以在其中添加更多案例而不会变得太大,并且很容易将您的目光从枚举值转移到返回值。

    DescriptionAttribute

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

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

    【讨论】:

    • 咳咳,如果你不介意打破一些风格规则,总是可以将枚举值的名称更改为它们的字符串代表!
    • 不幸的是,Dictionary 的表现会很糟糕:codeproject.com/KB/cs/EnumComparer.aspx
    • 只有当我看到一些实际证据表明它会导致瓶颈时,我才会担心这一点。与以往一样,使用最简单的代码,直到你有充分的理由去做其他事情。在我看来,“表现糟糕”和“表现不佳”之间存在很大差异。
    • @Ed Woodcock Integers 实现 IEquatable,并且 Dictionary 使用的 EqualityComparer 处理这种特殊情况。 Ayende (Rhino Mocks 的作者) 写了这篇文章:ayende.com/Blog/archive/2009/02/21/…
    • 我会支持 Jon - 在没有评论的情况下投反对票,解释为什么不帮助任何人
    【解决方案4】:

    以下解决方案有效(编译并运行)。 我看到两个问题:

    1. 您必须确保枚举是同步的。 (自动化测试可以为您做到这一点。)

    2. 您会依赖枚举在 .NET 中的类型不安全这一事实。

      enum Country
      {
          UnitedKingdom = 0,
          UnitedStates = 1,
          France = 2,
          Portugal = 3
      }
      
      enum CountryCode
      {
          UK = 0,
          US = 1,
          FR = 2,
          PT = 3
      }
      
      void Main()
      {
          string countryCode = ((CountryCode)Country.UnitedKingdom).ToString();
          Console.WriteLine(countryCode);
          countryCode = ((CountryCode)Country.Portugal).ToString();
          Console.WriteLine(countryCode);
      }
      

    【讨论】:

    • 虽然这可行。您可能希望明确说明这些值,而不是让编译器为您添加它们。
    【解决方案5】:

    我不得不暂时放弃这个项目的工作,回到它之后,我有了片刻的灵感。

    我创建了一个像这样的新类,而不是枚举:

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

    这样我可以在我的代码中使用 Country.UnitedKingdom 并且将使用值“UK”。

    我只是将此答案作为替代解决方案发布。

    尼尔

    【讨论】:

      【解决方案6】:

      每当我看到一个枚举时,我都觉得应该重构代码。为什么不创建一个 Country 类并添加方法来解决您试图绕过的一些障碍。给枚举赋值是一种更大的代码味道。

      为什么投反对票?我认为使用多态方法比使用枚举更好,这一点已被广泛接受。当您可以使用 ValueObject 设计时,使用枚举的理由为零。

      这是一篇关于该主题的好博文: http://devpinoy.org/blogs/cruizer/archive/2007/09/12/enums-are-evil.aspx

      【讨论】:

      • 我同意,在几乎所有情况下,当有人使用 Enum 时,他们可能应该仔细检查他们的代码,看看 Object 是否是更好的选择。
      • 这是迄今为止最干净的做事方式。我很惊讶你没有得到更多的支持。
      【解决方案7】:

      另一种没有提到的可能性是这样的:

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

      从这一点开始,您可以添加更多属性,但要注意向类添加了多少,以及添加了多少静态成员,因为它添加的内存膨胀可能使其不值得。

      我不认为这种策略在很多情况下是最好的方法,但是当您尝试将属性或属性添加到您希望能够本质上视为枚举的东西时,这是一个需要注意的选项。

      【讨论】:

      • 如果您担心静态对象的内存使用情况,您可以将它们设为静态只读属性并使用弱引用。 msdn.microsoft.com/en-us/library/ms404247.aspx
      • @Matthew Whited:你能提供一个在这种情况下如何使用弱引用的例子吗?
      【解决方案8】:

      只需使用DescriptionAttribute

      如果您只需要获取枚举值的字符串表示形式,则无需创建字典。看到这个example

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

      【讨论】:

      • 确实,这是我提到的另一种选择。但是,它的代码更多,并且它使用反射(除非我真的需要它,否则我通常会尽量避免)。当然,DescriptionAttribute 并没有什么神奇之处——您可以创建自己的属性并使用它。
      【解决方案9】:

      您可以创建一个扩展方法public static string ToShortString(this Country country)。在该方法中,您可以使用 Jon 建议的静态 Dictionary,也可以简单地使用 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";
              }
          }
      }
      

      【讨论】:

      • 只有 4 个值,我投票支持 switch 语句。当 switch 语句变得比字典实现更麻烦时,重构字典。
      • 是的,同意只有 4 个开关盒是正确的选择。感谢您添加代码,我还没有开始使用它。
      • +1 但请记住,列出的 4 个国家/地区数量略多=]
      【解决方案10】:
      var codes = new Dictionary<Country, string>() 
              { { Country.UnitedKingdom, "UK" },
              { Country.UnitedStates, "US" },
              { Country.France, "FR" } };
      Console.WriteLine(codes[Country.UnitedStates]);
      

      【讨论】:

        【解决方案11】:

        伪代码:

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

        【讨论】:

        • 这样的维护开销令人担忧。保持两个未连接的数据结构同步始终是一个危险信号。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-12
        • 1970-01-01
        • 2010-10-30
        相关资源
        最近更新 更多