【问题标题】:String representation of an Enum枚举的字符串表示
【发布时间】:2010-09-30 06:37:54
【问题描述】:

我有以下枚举:

public enum AuthenticationMethod
{
    FORMS = 1,
    WINDOWSAUTHENTICATION = 2,
    SINGLESIGNON = 3
}

但问题是,当我要求 AuthenticationMethod.FORMS 而不是 id 1 时,我需要“FORMS”这个词。

我找到了解决这个问题的以下方法(link):

首先我需要创建一个名为“StringValue”的自定义属性:

public class StringValue : System.Attribute
{
    private readonly string _value;

    public StringValue(string value)
    {
        _value = value;
    }

    public string Value
    {
        get { return _value; }
    }

}

然后我可以将此属性添加到我的枚举器中:

public enum AuthenticationMethod
{
    [StringValue("FORMS")]
    FORMS = 1,
    [StringValue("WINDOWS")]
    WINDOWSAUTHENTICATION = 2,
    [StringValue("SSO")]
    SINGLESIGNON = 3
}

当然我需要一些东西来检索那个 StringValue:

public static class StringEnum
{
    public static string GetStringValue(Enum value)
    {
        string output = null;
        Type type = value.GetType();

        //Check first in our cached results...

        //Look for our 'StringValueAttribute' 

        //in the field's custom attributes

        FieldInfo fi = type.GetField(value.ToString());
        StringValue[] attrs =
           fi.GetCustomAttributes(typeof(StringValue),
                                   false) as StringValue[];
        if (attrs.Length > 0)
        {
            output = attrs[0].Value;
        }

        return output;
    }
}

现在我已经有了获取枚举器字符串值的工具。 然后我可以这样使用它:

string valueOfAuthenticationMethod = StringEnum.GetStringValue(AuthenticationMethod.FORMS);

好吧,现在所有这些工作都像魅力一样,但我发现它的工作量很大。我想知道是否有更好的解决方案。

我也尝试了一些带有字典和静态属性的东西,但这也不是更好。

【问题讨论】:

  • 虽然您可能会觉得这很啰嗦,但它实际上是一种非常灵活的方式来处理其他事情。正如我的一位同事所指出的,这可以在许多情况下用于替换将数据库代码映射到枚举值等的 Enum Helpers...
  • 它是一个“枚举”,而不是一个“枚举器”。
  • MSDN 推荐带有“Attribute”后缀的后缀属性类。所以“类 StringValueAttribute”;)
  • 我同意@BenAlabaster 这实际上非常灵活。此外,您可以通过在静态方法中的 Enum 前面添加 this 来使其成为扩展方法。然后你可以做AuthenticationMethod.Forms.GetStringValue();
  • 这种方法使用反射来读取属性值,根据我的经验,如果您必须多次调用 GetStringValue(),它会非常慢。类型安全枚举模式更快。

标签: c# enums


【解决方案1】:

试试type-safe-enum 模式。

public sealed class AuthenticationMethod {

    private readonly String name;
    private readonly int value;

    public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (1, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (2, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (3, "SSN");        

    private AuthenticationMethod(int value, String name){
        this.name = name;
        this.value = value;
    }

    public override String ToString(){
        return name;
    }

}

更新 显式(或隐式)类型转换可以通过

  • 使用映射添加静态字段

    private static readonly Dictionary<string, AuthenticationMethod> instance = new Dictionary<string,AuthenticationMethod>();
    
    • n.b.为了在调用实例构造函数时“枚举成员”字段的初始化不会引发 NullReferenceException,请务必将 Dictionary 字段放在类中的“枚举成员”字段之前。这是因为静态字段初始化器是按声明顺序调用的,并且在静态构造函数之前,造成了奇怪且必要但令人困惑的情况,即可以在所有静态字段初始化之前和调用静态构造函数之前调用实例构造函数。
  • 在实例构造函数中填充这个映射

    instance[name] = this;
    
  • 并添加user-defined type conversion operator

    public static explicit operator AuthenticationMethod(string str)
    {
        AuthenticationMethod result;
        if (instance.TryGetValue(str, out result))
            return result;
        else
            throw new InvalidCastException();
    }
    

【讨论】:

  • 它看起来像一个枚举,但它不是一个枚举。我可以想象如果人们开始尝试比较 AuthenticationMethods 会导致一些有趣的问题。您可能还需要重载各种相等运算符。
  • @Ant:我不需要。由于我们每个 AuthenticationMethod 只有一个实例,因此从 Object 继承的引用相等性可以正常工作。
  • @tyriker:编译器可以。构造函数是私有的,因此您无法创建新实例。静态成员也不能通过实例访问。
  • @Jakub 非常有趣。我不得不玩弄它来弄清楚如何使用它,并意识到它的好处。它是一个公共的、非静态的类,但不能被实例化,你只能访问它的静态成员。基本上,它的行为就像一个枚举。但最好的部分......静态成员是类的类型,而不是通用字符串或 int。这是一个...... [等待它]......类型安全枚举!感谢您帮助我理解。
  • 喜欢这个解决方案——我遇到的唯一主要缺点是它在 switch 语句中不起作用——也就是说,我可以在切换,但不是这些。
【解决方案2】:

使用方法

Enum.GetName(Type MyEnumType,  object enumvariable)  

如(假设Shipper 是一个已定义的枚举)

Shipper x = Shipper.FederalExpress;
string s = Enum.GetName(typeof(Shipper), x);

Enum 类还有很多其他的静态方法也值得研究......

【讨论】:

  • 没错。我确实为字符串描述做了一个自定义属性,但那是因为我想要一个用户友好的版本(带有空格和其他特殊字符),可以轻松绑定到 ComboBox 等。
  • Enum.GetName 反映了枚举中的字段名称 - 与 .ToString() 相同。如果性能是一个问题,它可能是一个问题。不过,除非您要转换大量枚举,否则我不会担心。
  • 如果您需要具有额外功能的枚举,要考虑的另一个选项是使用结构“滚动您自己”...您添加静态只读命名属性来表示初始化为的枚举值生成结构的各个实例的构造函数...
  • 然后你可以添加你想要的任何其他结构成员,以实现你希望这个“枚举”拥有的任何功能......
  • 这里的问题是 GetName 不可本地化。这并不总是一个问题,但需要注意。
【解决方案3】:

您可以使用 ToString() 引用名称而不是值

Console.WriteLine("Auth method: {0}", AuthenticationMethod.Forms.ToString());

文档在这里:

http://msdn.microsoft.com/en-us/library/16c1xs4z.aspx

...如果您以 Pascal Case 命名您的枚举(就像我所做的那样 - 例如 ThisIsMyEnumValue = 1 等),那么您可以使用一个非常简单的正则表达式来打印友好的形式:

static string ToFriendlyCase(this string EnumString)
{
    return Regex.Replace(EnumString, "(?!^)([A-Z])", " $1");
}

可以很容易地从任何字符串中调用:

Console.WriteLine("ConvertMyCrazyPascalCaseSentenceToFriendlyCase".ToFriendlyCase());

输出:

将我的疯狂帕斯卡案例句子转换为友好案例

这节省了在房子周围跑来跑去创建自定义属性并将它们附加到您的枚举或使用查找表将枚举值与友好字符串结合起来,最重要的是它是自我管理的,可以用于任何 Pascal Case 字符串这是无限多的可重用。当然,它不允许您使用与您的解决方案提供的枚举相比不同的友好名称。

我确实喜欢您的原始解决方案,但适用于更复杂的场景。您可以将您的解决方案更进一步,让您的 GetStringValue 成为枚举的扩展方法,然后您就不需要像 StringEnum.GetStringValue 那样引用它...

public static string GetStringValue(this AuthenticationMethod value)
{
  string output = null;
  Type type = value.GetType();
  FieldInfo fi = type.GetField(value.ToString());
  StringValue[] attrs = fi.GetCustomAttributes(typeof(StringValue), false) as StringValue[];
  if (attrs.Length > 0)
    output = attrs[0].Value;
  return output;
}

然后您可以直接从您的枚举实例轻松访问它:

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());

【讨论】:

  • 如果“友好名称”需要空格,这将无济于事。如“表单认证”
  • 因此,请确保枚举以 FormsAuthentication 之类的大写字母命名,并在任何不在开头的大写字母之前插入一个空格。在字符串中插入空格不是火箭科学......
  • Pascal Case 名称的自动间距如果包含应大写的缩写,例如 XML 或 GPS,则会出现问题。
  • @RichardEv,没有完美的正则表达式,但这里有一个可以更好地使用缩写的正则表达式。 "(?!^)([^A-Z])([A-Z])", "$1 $2"。所以HereIsATEST 变成了Here Is ATEST
  • 做这些小“黑客”并不优雅,这就是它们的本质。我得到了 OP 的意思,我正在尝试找到一个类似的解决方案,即使用 Enums 的优雅但能够轻松访问相关的消息。我能想到的唯一解决方案是在枚举名称和字符串值之间应用某种映射,但这并没有解决维护字符串数据的问题(但是对于需要多个区域等的场景来说很实用) )
【解决方案4】:

不幸的是,在枚举上获取属性的反射非常慢:

看到这个问题:Anyone know a quick way to get to custom attributes on an enum value?

.ToString() 在枚举上也很慢。

您可以为枚举编写扩展方法:

public static string GetName( this MyEnum input ) {
    switch ( input ) {
        case MyEnum.WINDOWSAUTHENTICATION:
            return "Windows";
        //and so on
    }
}

这不是很好,但会很快并且不需要属性或字段名称的反射。


C#6 更新

如果您可以使用 C#6,那么新的nameof 运算符适用于枚举,因此nameof(MyEnum.WINDOWSAUTHENTICATION) 将在编译时转换为"WINDOWSAUTHENTICATION",使其成为最快的获取方式枚举名称。

请注意,这会将显式枚举转换为内联常量,因此它不适用于变量中的枚举。所以:

nameof(AuthenticationMethod.FORMS) == "FORMS"

但是……

var myMethod = AuthenticationMethod.FORMS;
nameof(myMethod) == "myMethod"

【讨论】:

  • 您可以一次获取属性值并将它们放入 Dictionary 以保持声明性方面。
  • 是的,当我们发现反射是瓶颈时,我们最终在一个包含大量枚举的应用中这样做了。
  • 感谢 Jon 和 Keith,我最终使用了您的 Dictionary 建议。效果很好(而且速度很快!)。
  • @JonSkeet 我知道这是旧的。但是如何实现呢?
  • @user919426:实现想要?把它们放在字典里?只需创建一个字典,理想情况下使用集合初始化器...不清楚您要的是什么。
【解决方案5】:

我使用扩展方法:

public static class AttributesHelperExtension
    {
        public static string ToDescription(this Enum value)
        {
            var da = (DescriptionAttribute[])(value.GetType().GetField(value.ToString())).GetCustomAttributes(typeof(DescriptionAttribute), false);
            return da.Length > 0 ? da[0].Description : value.ToString();
        }
}

现在用:

装饰enum
public enum AuthenticationMethod
{
    [Description("FORMS")]
    FORMS = 1,
    [Description("WINDOWSAUTHENTICATION")]
    WINDOWSAUTHENTICATION = 2,
    [Description("SINGLESIGNON ")]
    SINGLESIGNON = 3
}

当你打电话时

AuthenticationMethod.FORMS.ToDescription() 你会得到"FORMS"

【讨论】:

  • 我必须添加using System.ComponentModel; 此外,此方法仅在您希望字符串值与枚举名称相同时才有效。 OP 想要一个不同的值。
  • 你不是说打电话给AuthenticationMethod.FORMS.ToDescription()吗?
【解决方案6】:

只需使用ToString() 方法

public enum any{Tomato=0,Melon,Watermelon}

要引用字符串Tomato,只需使用

any.Tomato.ToString();

【讨论】:

  • 哇。那很简单。我知道 OP 想要添加自定义字符串描述,但这正是我所需要的。回想起来,我应该知道尝试这个,但我走的是 Enum.GetName 路线。
  • @Brent 因为大多数情况下您的 .ToString() 值与您需要的用户友好值不同。
  • @Brent - 因为这与所提出的问题不同。要问的问题是如何从已分配枚举值的变量中获取此字符串。这在运行时是动态的。这是检查类型的定义并在运行时设置。
  • @Hogan - ToString() 也适用于变量:any fruit = any.Tomato; string tomato = fruit.ToString();
  • @LiborV - 请记住这是在 09 年编写的 - 当时 C# 不同,枚举实例上的 ToString() 做了不同的事情。
【解决方案7】:

.Net 4.0 及更高版本非常简单的解决方案。不需要其他代码。

public enum MyStatus
{
    Active = 1,
    Archived = 2
}

要获取有关的字符串,只需使用:

MyStatus.Active.ToString("f");

MyStatus.Archived.ToString("f");`

值将是“活动”或“存档”。

要在调用Enum.ToString 时查看不同的字符串格式(上面的“f”),请参阅此Enumeration Format Strings 页面

【讨论】:

    【解决方案8】:

    我使用 System.ComponentModel 命名空间中的 Description 属性。只需装饰枚举,然后使用此代码检索它:

    public static string GetDescription<T>(this object enumerationValue)
                where T : struct
            {
                Type type = enumerationValue.GetType();
                if (!type.IsEnum)
                {
                    throw new ArgumentException("EnumerationValue must be of Enum type", "enumerationValue");
                }
    
                //Tries to find a DescriptionAttribute for a potential friendly name
                //for the enum
                MemberInfo[] memberInfo = type.GetMember(enumerationValue.ToString());
                if (memberInfo != null && memberInfo.Length > 0)
                {
                    object[] attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
    
                    if (attrs != null && attrs.Length > 0)
                    {
                        //Pull out the description value
                        return ((DescriptionAttribute)attrs[0]).Description;
                    }
                }
                //If we have no description attribute, just return the ToString of the enum
                return enumerationValue.ToString();
    
            }
    

    举个例子:

    public enum Cycle : int
    {        
       [Description("Daily Cycle")]
       Daily = 1,
       Weekly,
       Monthly
    }
    

    这段代码非常适合不需要“友好名称”的枚举,并且只会返回枚举的 .ToString()。

    【讨论】:

      【解决方案9】:

      我真的很喜欢 Jakub Šturc 的回答,但缺点是您不能将它与 switch-case 语句一起使用。这是他的答案的略微修改版本,可以与 switch 语句一起使用:

      public sealed class AuthenticationMethod
      {
          #region This code never needs to change.
          private readonly string _name;
          public readonly Values Value;
      
          private AuthenticationMethod(Values value, String name){
              this._name = name;
              this.Value = value;
          }
      
          public override String ToString(){
              return _name;
          }
          #endregion
      
          public enum Values
          {
              Forms = 1,
              Windows = 2,
              SSN = 3
          }
      
          public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (Values.Forms, "FORMS");
          public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (Values.Windows, "WINDOWS");
          public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (Values.SSN, "SSN");
      }
      

      因此,您可以获得 Jakub Šturc 答案的所有好处,而且我们可以将它与 switch 语句一起使用,如下所示:

      var authenticationMethodVariable = AuthenticationMethod.FORMS;  // Set the "enum" value we want to use.
      var methodName = authenticationMethodVariable.ToString();       // Get the user-friendly "name" of the "enum" value.
      
      // Perform logic based on which "enum" value was chosen.
      switch (authenticationMethodVariable.Value)
      {
          case authenticationMethodVariable.Values.Forms: // Do something
              break;
          case authenticationMethodVariable.Values.Windows: // Do something
              break;
          case authenticationMethodVariable.Values.SSN: // Do something
              break;      
      }
      

      【讨论】:

      • 一个更短的解决方案是删除枚举 {} 并保持静态计数您已构建的枚举数量。这也带来了您不必将创建的新实例添加到枚举列表的好处。例如public static int nextAvailable { get; private set; } 然后在构造函数中 this.Value = nextAvailable++;
      • 有趣的想法@kjhf。不过我担心的是,如果有人重新排序代码,那么分配给枚举值的值也可能会改变。例如,这可能导致在将枚举值保存到文件/数据库时检索到错误的枚举值,“new AuthenticationMethod(...)”行的顺序被更改(例如,删除一个),然后再次运行应用程序并从文件/数据库中检索枚举值;枚举值可能与最初保存的 AuthenticationMethod 不匹配。
      • 好点 - 虽然我希望在这些特殊情况下人们不会依赖枚举的整数值(或重新排序枚举代码。) - 这个值纯粹用作开关,可能是一个.Equals() 和 .GetHashCode() 的替代方案。如果担心,您可以随时使用“请勿重新排序”发表大量评论:p
      • 您不能重载= 运算符以允许切换工作吗?我是在 VB 中完成的,现在可以在 select case 语句中使用它。
      • @user1318499 不,C# 对 switch 语句的规则比 VB 更严格。您不能将类实例用于 Case 语句;你只能使用常量原语。
      【解决方案10】:

      我结合了上面的几个建议,并结合了一些缓存。现在,我从网上某处找到的一些代码中得到了这个想法,但我既不记得我从哪里得到它,也不记得它是在哪里找到的。因此,如果有人发现类似的东西,请在出处发表评论。

      无论如何,使用涉及类型转换器,因此如果您绑定到 UI,它“正常工作”。您可以使用 Jakub 的模式进行扩展,通过从类型转换器初始化为静态方法来快速查找代码。

      基本用法如下所示

      [TypeConverter(typeof(CustomEnumTypeConverter<MyEnum>))]
      public enum MyEnum
      {
          // The custom type converter will use the description attribute
          [Description("A custom description")]
          ValueWithCustomDescription,
      
         // This will be exposed exactly.
         Exact
      }
      

      自定义枚举类型转换器的代码如下:

      public class CustomEnumTypeConverter<T> : EnumConverter
          where T : struct
      {
          private static readonly Dictionary<T,string> s_toString = 
            new Dictionary<T, string>();
      
          private static readonly Dictionary<string, T> s_toValue = 
            new Dictionary<string, T>();
      
          private static bool s_isInitialized;
      
          static CustomEnumTypeConverter()
          {
              System.Diagnostics.Debug.Assert(typeof(T).IsEnum,
                "The custom enum class must be used with an enum type.");
          }
      
          public CustomEnumTypeConverter() : base(typeof(T))
          {
              if (!s_isInitialized)
              {
                  Initialize();
                  s_isInitialized = true;
              }
          }
      
          protected void Initialize()
          {
              foreach (T item in Enum.GetValues(typeof(T)))
              {
                  string description = GetDescription(item);
                  s_toString[item] = description;
                  s_toValue[description] = item;
              }
          }
      
          private static string GetDescription(T optionValue)
          {
              var optionDescription = optionValue.ToString();
              var optionInfo = typeof(T).GetField(optionDescription);
              if (Attribute.IsDefined(optionInfo, typeof(DescriptionAttribute)))
              {
                  var attribute = 
                    (DescriptionAttribute)Attribute.
                       GetCustomAttribute(optionInfo, typeof(DescriptionAttribute));
                  return attribute.Description;
              }
              return optionDescription;
          }
      
          public override object ConvertTo(ITypeDescriptorContext context, 
             System.Globalization.CultureInfo culture, 
             object value, Type destinationType)
          {
              var optionValue = (T)value;
      
              if (destinationType == typeof(string) && 
                  s_toString.ContainsKey(optionValue))
              {
                  return s_toString[optionValue];
              }
      
              return base.ConvertTo(context, culture, value, destinationType);
          }
      
          public override object ConvertFrom(ITypeDescriptorContext context, 
             System.Globalization.CultureInfo culture, object value)
          {
              var stringValue = value as string;
      
              if (!string.IsNullOrEmpty(stringValue) && s_toValue.ContainsKey(stringValue))
              {
                  return s_toValue[stringValue];
              }
      
              return base.ConvertFrom(context, culture, value);
          }
      }
      

      }

      【讨论】:

      • 怎么用?谢谢。例如:MyEnum.ValueWithCustomDescription.??() 什么的?
      • 这个答案是大约十年前写的,所以我真的不确定原始上下文。 SO的问题在于它永远存在。我相信这与添加要在 WinForms 或 WPF 等 UI 上显示的数据有关,在这种情况下,只需将属性或集合绑定到 UI 控件即可直接获取类型转换器。
      【解决方案11】:

      在您的问题中,您从未说过实际上在任何地方都需要枚举的数值。

      如果你不需要并且只需要一个字符串类型的枚举(它不是一个整数类型,所以不能是枚举的基础),这是一种方法:

          static class AuthenticationMethod
          {
              public static readonly string
                  FORMS = "Forms",
                  WINDOWSAUTHENTICATION = "WindowsAuthentication";
          }
      

      你可以使用与枚举相同的语法来引用它

      if (bla == AuthenticationMethod.FORMS)
      

      它会比使用数值(比较字符串而不是数字)慢一点,但从好的方面来说,它不使用反射(慢)来访问字符串。

      【讨论】:

      • 如果您使用“const”而不是“static readonly”,那么您可以在 switch 语句中使用这些值作为 case 标签。
      【解决方案12】:

      更新: 8 年后访问此页面,在很长一段时间没有接触 C# 之后,看起来我的答案不再是最佳解决方案。我真的很喜欢与属性函数绑定的转换器解决方案。

      如果您正在阅读本文,请确保您还查看了其他答案。
      (提示:它们在这个之上)


      和你们大多数人一样,我真的很喜欢所选的answer by Jakub Šturc,但我也很讨厌复制粘贴代码,并尽量少做。

      所以我决定我想要一个 EnumBase 类,它的大部分功能都继承/内置,让我专注于内容而不是行为。

      这种方法的主要问题在于,尽管 Enum 值是类型安全的实例,但交互是与 Enum Class 类型的静态实现。 所以在泛型魔法的帮助下,我想我终于得到了正确的组合。 希望有人和我一样觉得这很有用。

      我将从 Jakub 的示例开始,但使用继承和泛型:

      public sealed class AuthenticationMethod : EnumBase<AuthenticationMethod, int>
      {
          public static readonly AuthenticationMethod FORMS =
              new AuthenticationMethod(1, "FORMS");
          public static readonly AuthenticationMethod WINDOWSAUTHENTICATION =
              new AuthenticationMethod(2, "WINDOWS");
          public static readonly AuthenticationMethod SINGLESIGNON =
              new AuthenticationMethod(3, "SSN");
      
          private AuthenticationMethod(int Value, String Name)
              : base( Value, Name ) { }
          public new static IEnumerable<AuthenticationMethod> All
          { get { return EnumBase<AuthenticationMethod, int>.All; } }
          public static explicit operator AuthenticationMethod(string str)
          { return Parse(str); }
      }
      

      这是基类:

      using System;
      using System.Collections.Generic;
      using System.Linq; // for the .AsEnumerable() method call
      
      // E is the derived type-safe-enum class
      // - this allows all static members to be truly unique to the specific
      //   derived class
      public class EnumBase<E, T> where E: EnumBase<E, T>
      {
          #region Instance code
          public T Value { get; private set; }
          public string Name { get; private set; }
      
          protected EnumBase(T EnumValue, string Name)
          {
              Value = EnumValue;
              this.Name = Name;
              mapping.Add(Name, this);
          }
      
          public override string ToString() { return Name; }
          #endregion
      
          #region Static tools
          static private readonly Dictionary<string, EnumBase<E, T>> mapping;
          static EnumBase() { mapping = new Dictionary<string, EnumBase<E, T>>(); }
          protected static E Parse(string name)
          {
              EnumBase<E, T> result;
              if (mapping.TryGetValue(name, out result))
              {
                  return (E)result;
              }
      
              throw new InvalidCastException();
          }
          // This is protected to force the child class to expose it's own static
          // method.
          // By recreating this static method at the derived class, static
          // initialization will be explicit, promising the mapping dictionary
          // will never be empty when this method is called.
          protected static IEnumerable<E> All
          { get { return mapping.Values.AsEnumerable().Cast<E>(); } }
          #endregion
      }
      

      【讨论】:

      【解决方案13】:

      我是如何作为扩展方法解决这个问题的:

      using System.ComponentModel;
      public static string GetDescription(this Enum value)
      {
          var descriptionAttribute = (DescriptionAttribute)value.GetType()
              .GetField(value.ToString())
              .GetCustomAttributes(false)
              .Where(a => a is DescriptionAttribute)
              .FirstOrDefault();
      
          return descriptionAttribute != null ? descriptionAttribute.Description : value.ToString();
      }
      

      枚举:

      public enum OrderType
      {
          None = 0,
          [Description("New Card")]
          NewCard = 1,
          [Description("Reload")]
          Refill = 2
      }
      

      用法(其中 o.OrderType 是与枚举同名的属性):

      o.OrderType.GetDescription()
      

      这给了我一串“New Card”或“Reload”,而不是实际的枚举值 NewCard 和 Refill。

      【讨论】:

      • 为了完整起见,您应该包含您的 DescriptionAttribute 类的副本。
      • 伯尼,DescriptionAttribute 在 System.ComponentModel 中
      【解决方案14】:

      如果您来这里是为了实现一个简单的“枚举”,但其值是字符串而不是整数,那么这里是最简单的解决方案:

          public sealed class MetricValueList
          {
              public static readonly string Brand = "A4082457-D467-E111-98DC-0026B9010912";
              public static readonly string Name = "B5B5E167-D467-E111-98DC-0026B9010912";
          }
      

      实施:

      var someStringVariable = MetricValueList.Brand;
      

      【讨论】:

      • 将变量设为 const 可能比使用 static readonly 更好。
      • consts 不适用于可公开访问的类,因为它们是在编译时烘焙的,如果不使用 consts 重新编译整个代码,就无法替换第三方 DLL。consts 与 static readonly 的性能偏移可以忽略不计.
      【解决方案15】:

      我同意 Keith,但我还不能投票。

      我使用静态方法和 swith 语句来准确返回我想要的。在我存储 tinyint 的数据库中,我的代码只使用实际的枚举,所以字符串是针对 UI 要求的。经过多次测试,这导致了最佳性能和对输出的最大控制。

      public static string ToSimpleString(this enum)
      {
           switch (enum)
           {
               case ComplexForms:
                   return "ComplexForms";
                   break;
           }
      }
      
      public static string ToFormattedString(this enum)
      {
           switch (enum)
           {
               case ComplexForms:
                   return "Complex Forms";
                   break;
           }
      }
      

      但是,根据某些说法,这可能会导致维护噩梦和一些代码异味。我尽量留意那些很长且有很多枚举的枚举,或者那些经常更改的枚举。否则,这对我来说是一个很好的解决方案。

      【讨论】:

        【解决方案16】:

        当我遇到这个问题时,我首先尝试找到几个问题的答案:

        • 我的枚举值的名称是否足够友好,或者我需要提供更友好的名称?
        • 我需要往返吗?也就是说,我是否需要获取文本值并将它们解析为枚举值?
        • 这是我需要为我的项目中的许多枚举做的事情,还是只需要一个?
        • 我将在哪些类型的 UI 元素中呈现此信息 - 特别是,我将绑定到 UI 还是使用属性表?
        • 这需要本地化吗?

        最简单的方法是使用Enum.GetValue(并支持使用Enum.Parse 进行往返)。正如 Steve Mitcham 建议的那样,通常还值得构建一个 TypeConverter 来支持 UI 绑定。 (当你使用属性表时,没有必要构建TypeConverter,这是属性表的优点之一。虽然上帝知道他们有自己的问题。)

        一般来说,如果上述问题的答案表明这不起作用,我的下一步是创建并填充静态Dictionary&lt;MyEnum, string&gt;,或者可能是Dictionary&lt;Type, Dictionary&lt;int, string&gt;&gt;。我倾向于跳过中间的 decorate-the-code-with-attributes 步骤,因为接下来通常需要在部署后更改友好值(通常,但并非总是如此,因为本地化)。

        【讨论】:

          【解决方案17】:

          我想将此作为对下面引用的帖子的评论发布,但因为我没有足够的代表而无法发布 - 所以请不要投反对票。该代码包含一个错误,我想向尝试使用此解决方案的个人指出这一点:

          [TypeConverter(typeof(CustomEnumTypeConverter(typeof(MyEnum))]
          public enum MyEnum
          {
            // The custom type converter will use the description attribute
            [Description("A custom description")]
            ValueWithCustomDescription,
            // This will be exposed exactly.
            Exact
          }
          

          应该是

          [TypeConverter(typeof(CustomEnumTypeConverter<MyEnum>))]
          public enum MyEnum
          {
            // The custom type converter will use the description attribute
            [Description("A custom description")]
            ValueWithCustomDescription,
          
            // This will be exposed exactly.
            Exact
          }
          

          太棒了!

          【讨论】:

            【解决方案18】:

            我创建了一个基类,用于在 .NET 中创建字符串值枚举。它只是一个 C# 文件,您可以将其复制并粘贴到您的项目中,或通过名为 StringEnum 的 NuGet 包安装。 GitHub Repo

            • 如果类使用 xml 注释 &lt;completitionlist&gt; 进行注释,Intellisense 将建议枚举名称。 (适用于 C# 和 VB)

            • 用法类似于常规枚举:
            ///<completionlist cref="HexColor"/> 
            class HexColor : StringEnum<HexColor>
            {
                public static readonly HexColor Blue = Create("#FF0000");
                public static readonly HexColor Green = Create("#00FF00");
                public static readonly HexColor Red = Create("#000FF");
            }
            
                // Static Parse Method
                HexColor.Parse("#FF0000") // => HexColor.Red
                HexColor.Parse("#ff0000", caseSensitive: false) // => HexColor.Red
                HexColor.Parse("invalid") // => throws InvalidOperationException
            
                // Static TryParse method.
                HexColor.TryParse("#FF0000") // => HexColor.Red
                HexColor.TryParse("#ff0000", caseSensitive: false) // => HexColor.Red
                HexColor.TryParse("invalid") // => null
            
                // Parse and TryParse returns the preexistent instances
                object.ReferenceEquals(HexColor.Parse("#FF0000"), HexColor.Red) // => true
            
                // Conversion from your `StringEnum` to `string`
                string myString1 = HexColor.Red.ToString(); // => "#FF0000"
                string myString2 = HexColor.Red; // => "#FF0000" (implicit cast)
            

            安装:

            • 将以下 StringEnum 基类粘贴到您的项目中。 (latest version)
            • 或安装StringEnum NuGet 包,它基于.Net Standard 1.0,因此它在.Net Core >= 1.0、.Net Framework >= 4.5、Mono >= 4.6 等上运行。
                /// <summary>
                /// Base class for creating string-valued enums in .NET.<br/>
                /// Provides static Parse() and TryParse() methods and implicit cast to string.
                /// </summary>
                /// <example> 
                /// <code>
                /// class Color : StringEnum &lt;Color&gt;
                /// {
                ///     public static readonly Color Blue = Create("Blue");
                ///     public static readonly Color Red = Create("Red");
                ///     public static readonly Color Green = Create("Green");
                /// }
                /// </code>
                /// </example>
                /// <typeparam name="T">The string-valued enum type. (i.e. class Color : StringEnum&lt;Color&gt;)</typeparam>
                public abstract class StringEnum<T> : IEquatable<T> where T : StringEnum<T>, new()
                {
                    protected string Value;
                    private static Dictionary<string, T> valueDict = new Dictionary<string, T>();
                    protected static T Create(string value)
                    {
                        if (value == null)
                            return null; // the null-valued instance is null.
            
                        var result = new T() { Value = value };
                        valueDict.Add(value, result);
                        return result;
                    }
            
                    public static implicit operator string(StringEnum<T> enumValue) => enumValue.Value;
                    public override string ToString() => Value;
            
                    public static bool operator !=(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value != o2?.Value;
                    public static bool operator ==(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value == o2?.Value;
            
                    public override bool Equals(object other) => this.Value.Equals((other as T)?.Value ?? (other as string));
                    bool IEquatable<T>.Equals(T other) => this.Value.Equals(other.Value);
                    public override int GetHashCode() => Value.GetHashCode();
            
                    /// <summary>
                    /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else throws InvalidOperationException.
                    /// </summary>
                    /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
                    /// <param name="caseSensitive">If true, the strings must match case and takes O(log n). False allows different case but is little bit slower (O(n))</param>
                    public static T Parse(string value, bool caseSensitive = true)
                    {
                        var result = TryParse(value, caseSensitive);
                        if (result == null)
                            throw new InvalidOperationException((value == null ? "null" : $"'{value}'") + $" is not a valid {typeof(T).Name}");
            
                        return result;
                    }
            
                    /// <summary>
                    /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else returns null.
                    /// </summary>
                    /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
                    /// <param name="caseSensitive">If true, the strings must match case. False allows different case but is slower: O(n)</param>
                    public static T TryParse(string value, bool caseSensitive = true)
                    {
                        if (value == null) return null;
                        if (valueDict.Count == 0) System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // force static fields initialization
                        if (caseSensitive)
                        {
                            if (valueDict.TryGetValue(value, out T item))
                                return item;
                            else
                                return null;
                        }
                        else
                        {
                            // slower O(n) case insensitive search
                            return valueDict.FirstOrDefault(f => f.Key.Equals(value, StringComparison.OrdinalIgnoreCase)).Value;
                            // Why Ordinal? => https://esmithy.net/2007/10/15/why-stringcomparisonordinal-is-usually-the-right-choice/
                        }
                    }
                }
            

            【讨论】:

              【解决方案19】:

              我的变种

              public struct Colors
              {
                  private String current;
              
                  private static string red = "#ff0000";
                  private static string green = "#00ff00";
                  private static string blue = "#0000ff";
              
                  private static IList<String> possibleColors; 
              
                  public static Colors Red { get { return (Colors) red; } }
                  public static Colors Green { get { return (Colors) green; } }
                  public static Colors Blue { get { return (Colors) blue; } }
              
                  static Colors()
                  {
                      possibleColors = new List<string>() {red, green, blue};
                  }
              
                  public static explicit operator String(Colors value)
                  {
                      return value.current;
                  }
              
                  public static explicit operator Colors(String value)
                  {
                      if (!possibleColors.Contains(value))
                      {
                          throw new InvalidCastException();
                      }
              
                      Colors color = new Colors();
                      color.current = value;
                      return color;
                  }
              
                  public static bool operator ==(Colors left, Colors right)
                  {
                      return left.current == right.current;
                  }
              
                  public static bool operator !=(Colors left, Colors right)
                  {
                      return left.current != right.current;
                  }
              
                  public bool Equals(Colors other)
                  {
                      return Equals(other.current, current);
                  }
              
                  public override bool Equals(object obj)
                  {
                      if (ReferenceEquals(null, obj)) return false;
                      if (obj.GetType() != typeof(Colors)) return false;
                      return Equals((Colors)obj);
                  }
              
                  public override int GetHashCode()
                  {
                      return (current != null ? current.GetHashCode() : 0);
                  }
              
                  public override string ToString()
                  {
                      return current;
                  }
              }
              

              代码看起来有点难看,但是这个结构的用法很有代表性。

              Colors color1 = Colors.Red;
              Console.WriteLine(color1); // #ff0000
              
              Colors color2 = (Colors) "#00ff00";
              Console.WriteLine(color2); // #00ff00
              
              // Colors color3 = "#0000ff"; // Compilation error
              // String color4 = Colors.Red; // Compilation error
              
              Colors color5 = (Colors)"#ff0000";
              Console.WriteLine(color1 == color5); // True
              
              Colors color6 = (Colors)"#00ff00";
              Console.WriteLine(color1 == color6); // False
              

              另外,我认为,如果需要大量此类枚举,可能会使用代码生成(例如 T4)。

              【讨论】:

                【解决方案20】:

                选项 1:

                public sealed class FormsAuth
                {
                     public override string ToString{return "Forms Authtentication";}
                }
                public sealed class WindowsAuth
                {
                     public override string ToString{return "Windows Authtentication";}
                }
                
                public sealed class SsoAuth
                {
                     public override string ToString{return "SSO";}
                }
                

                然后

                object auth = new SsoAuth(); //or whatever
                
                //...
                //...
                // blablabla
                
                DoSomethingWithTheAuth(auth.ToString());
                

                选项 2:

                public enum AuthenticationMethod
                {
                        FORMS = 1,
                        WINDOWSAUTHENTICATION = 2,
                        SINGLESIGNON = 3
                }
                
                public class MyClass
                {
                    private Dictionary<AuthenticationMethod, String> map = new Dictionary<AuthenticationMethod, String>();
                    public MyClass()
                    {
                         map.Add(AuthenticationMethod.FORMS,"Forms Authentication");
                         map.Add(AuthenticationMethod.WINDOWSAUTHENTICATION ,"Windows Authentication");
                         map.Add(AuthenticationMethod.SINGLESIGNON ,"SSo Authentication");
                    }
                }
                

                【讨论】:

                  【解决方案21】:

                  如果您考虑一下我们要解决的问题,那根本就不是我们需要的枚举。我们需要一个允许一定数量的值相互关联的对象;换句话说,定义一个类。

                  Jakub Šturc 的类型安全枚举模式是我在这里看到的最佳选择。

                  看看吧:

                  • 它有一个私有构造函数,所以只有类本身可以定义允许的值。
                  • 它是一个密封类,因此不能通过继承修改值。
                  • 它是类型安全的,允许您的方法只需要该类型。
                  • 访问这些值不会影响反射性能。
                  • 最后,它可以修改为将两个以上的字段关联在一起,例如名称、描述和数值。

                  【讨论】:

                    【解决方案22】:

                    对我来说,务实的做法是类中类,示例:

                    public class MSEModel
                    {
                        class WITS
                        {
                            public const string DATE = "5005";
                            public const string TIME = "5006";
                            public const string MD = "5008";
                            public const string ROP = "5075";
                            public const string WOB = "5073";
                            public const string RPM = "7001";
                    ... 
                        }
                    

                    【讨论】:

                      【解决方案23】:

                      这是完成将字符串与枚举关联的任务的另一种方法:

                      struct DATABASE {
                          public enum enums {NOTCONNECTED, CONNECTED, ERROR}
                          static List<string> strings =
                              new List<string>() {"Not Connected", "Connected", "Error"};
                      
                          public string GetString(DATABASE.enums value) {
                              return strings[(int)value];
                          }
                      }
                      

                      这个方法是这样调用的:

                      public FormMain() {
                          DATABASE dbEnum;
                      
                          string enumName = dbEnum.GetString(DATABASE.enums.NOTCONNECTED);
                      }
                      

                      您可以将相关的枚举分组到它们自己的结构中。由于此方法使用枚举类型,因此您可以在进行GetString() 调用时使用 Intellisense 显示枚举列表。

                      您可以选择在 DATABASE 结构上使用 new 运算符。不使用它意味着在第一次调用GetString() 之前不会分配字符串List

                      【讨论】:

                        【解决方案24】:

                        这里有很多很好的答案,但在我的情况下并没有解决我想要的“字符串枚举”,即:

                        1. 可在 switch 语句中使用,例如 switch(myEnum)
                        2. 可用于函数参数,例如foo(myEnum 类型)
                        3. 可以参考,例如myEnum.FirstElement
                        4. 我可以使用字符串,例如foo("FirstElement") == foo(myEnum.FirstElement)

                        1,2 & 4 实际上可以用字符串的 C# Typedef 来解决(因为字符串在 c# 中是可切换的)

                        3 可以通过静态常量字符串来解决。所以如果你有同样的需求,这是最简单的方法:

                        public sealed class Types
                        {
                        
                            private readonly String name;
                        
                            private Types(String name)
                            {
                                this.name = name;
                        
                            }
                        
                            public override String ToString()
                            {
                                return name;
                            }
                        
                            public static implicit operator Types(string str)
                            {
                                return new Types(str);
                        
                            }
                            public static implicit operator string(Types str)
                            {
                                return str.ToString();
                            }
                        
                        
                            #region enum
                        
                            public const string DataType = "Data";
                            public const string ImageType = "Image";
                            public const string Folder = "Folder";
                            #endregion
                        
                        }
                        

                        这允许例如:

                            public TypeArgs(Types SelectedType)
                            {
                                Types SelectedType = SelectedType
                            }
                        

                        public TypeObject CreateType(Types type)
                            {
                                switch (type)
                                {
                        
                                    case Types.ImageType:
                                      //
                                        break;
                        
                                    case Types.DataType:
                                     //
                                        break;
                        
                                }
                            }
                        

                        可以使用字符串或类型调用 CreateType。 但缺点是任何字符串都会自动成为有效的枚举,这可以被修改,但它需要某种 init 函数......或者可能使它们显式地转换为内部?

                        现在,如果一个 int 值对您很重要(也许是为了比较速度),您可以使用 Jakub Šturc 的一些想法并做出一些疯狂的,这是我的尝试:

                            public sealed class Types
                        {
                            private static readonly Dictionary<string, Types> strInstance = new Dictionary<string, Types>();
                            private static readonly Dictionary<int, Types> intInstance = new Dictionary<int, Types>();
                        
                            private readonly String name;
                            private static int layerTypeCount = 0;
                            private int value;
                            private Types(String name)
                            {
                                this.name = name;
                                value = layerTypeCount++;
                                strInstance[name] = this;
                                intInstance[value] = this;
                            }
                        
                            public override String ToString()
                            {
                                return name;
                            }
                        
                        
                            public static implicit operator Types(int val)
                            {
                                Types result;
                                if (intInstance.TryGetValue(val, out result))
                                    return result;
                                else
                                    throw new InvalidCastException();
                            }
                        
                            public static implicit operator Types(string str)
                            {
                                Types result;
                                if (strInstance.TryGetValue(str, out result))
                                {
                                    return result;
                                }
                                else
                                {
                                    result = new Types(str);
                                    return result;
                                }
                        
                            }
                            public static implicit operator string(Types str)
                            {
                                return str.ToString();
                            }
                        
                            public static bool operator ==(Types a, Types b)
                            {
                                return a.value == b.value;
                            }
                            public static bool operator !=(Types a, Types b)
                            {
                                return a.value != b.value;
                            }
                        
                            #region enum
                        
                            public const string DataType = "Data";
                            public const string ImageType = "Image";
                        
                            #endregion
                        
                        }
                        

                        当然“类型 bob = 4;”除非您先初始化它们,否则将毫无意义,这会有点失败......

                        但理论上 TypeA == TypeB 会更快...

                        【讨论】:

                          【解决方案25】:

                          如果我对您的理解正确,您可以简单地使用 .ToString() 从值中检索枚举的名称(假设它已经被转换为枚举); 如果您有裸 int (可以说来自数据库或其他东西),您可以先将其转换为枚举。 以下两种方法都会为您提供枚举名称。

                          AuthenticationMethod myCurrentSetting = AuthenticationMethod.FORMS;
                          Console.WriteLine(myCurrentSetting); // Prints: FORMS
                          string name = Enum.GetNames(typeof(AuthenticationMethod))[(int)myCurrentSetting-1];
                          Console.WriteLine(name); // Prints: FORMS
                          

                          但请记住,第二种技术假设您使用的是整数,并且您的索引是基于 1 的(而不是基于 0 的)。相比之下,函数 GetNames 也很重,每次调用它时都会生成一个完整的数组。 正如您在第一种技术中看到的那样, .ToString() 实际上是隐式调用的。 当然,这两个都已经在答案中提到了,我只是想澄清它们之间的区别。

                          【讨论】:

                            【解决方案26】:

                            旧帖,但是...

                            这个问题的答案实际上可能非常简单。使用Enum.ToString()函数

                            这个函数有6个重载,你可以使用Enum.Tostring("F")或者Enum.ToString()来返回字符串值。无需为其他事情烦恼。这是working Demo

                            请注意,此解决方案可能不适用于所有编译器 (this demo does not work as expected),但至少适用于最新的编译器。

                            【讨论】:

                              【解决方案27】:

                              基于 MSDN:http://msdn.microsoft.com/en-us/library/cc138362.aspx

                              foreach (string str in Enum.GetNames(typeof(enumHeaderField)))
                              {
                                  Debug.WriteLine(str);
                              }
                              

                              str 将是字段的名称

                              【讨论】:

                              【解决方案28】:

                              好吧,在阅读了以上所有内容后,我觉得这些家伙将枚举数转换为字符串的问题过于复杂了。 我喜欢在枚举字段上拥有属性的想法,但我认为属性主要用于元数据,但在你的情况下,我认为你所需要的只是某种本地化。

                              public enum Color 
                              { Red = 1, Green = 2, Blue = 3}
                              
                              
                              public static EnumUtils 
                              {
                                 public static string GetEnumResourceString(object enumValue)
                                  {
                                      Type enumType = enumValue.GetType();
                                      string value = Enum.GetName(enumValue.GetType(), enumValue);
                                      string resourceKey = String.Format("{0}_{1}", enumType.Name, value);
                                      string result = Resources.Enums.ResourceManager.GetString(resourceKey);
                                      if (string.IsNullOrEmpty(result))
                                      {
                                          result = String.Format("{0}", value);
                                      }
                                      return result;
                                  }
                              }
                              

                              现在如果我们尝试调用上面的方法,我们可以这样调用它

                              public void Foo()
                              {
                                var col = Color.Red;
                                Console.WriteLine (EnumUtils.GetEnumResourceString (col));
                              }
                              

                              您只需创建一个包含所有枚举值和相应字符串的资源文件

                              资源名称 资源值 Color_Red 我的字符串颜色为红色 颜色_Blue Blueeey 颜色_绿巨人颜色

                              实际上非常好的一点是,如果您需要本地化您的应用程序,这将非常有帮助,因为您需要做的只是用您的新语言创建另一个资源文件!和沃拉!

                              【讨论】:

                                【解决方案29】:

                                当我遇到这种情况时,我会提出以下解决方案。

                                作为一个消费类,你可以拥有

                                using System;
                                using System.Collections.Generic;
                                using System.Linq;
                                using System.Text;
                                
                                namespace MyApp.Dictionaries
                                {
                                    class Greek
                                    {
                                
                                        public static readonly string Alpha = "Alpha";
                                        public static readonly string Beta = "Beta";
                                        public static readonly string Gamma = "Gamma";
                                        public static readonly string Delta = "Delta";
                                
                                
                                        private static readonly BiDictionary<int, string> Dictionary = new BiDictionary<int, string>();
                                
                                
                                        static Greek() {
                                            Dictionary.Add(1, Alpha);
                                            Dictionary.Add(2, Beta);
                                            Dictionary.Add(3, Gamma);
                                            Dictionary.Add(4, Delta);
                                        }
                                
                                        public static string getById(int id){
                                            return Dictionary.GetByFirst(id);
                                        }
                                
                                        public static int getByValue(string value)
                                        {
                                            return Dictionary.GetBySecond(value);
                                        }
                                
                                    }
                                }
                                

                                并使用双向字典: 基于此 (https://stackoverflow.com/a/255638/986160) 假设键将与字典中的单个值相关联并且类似于 (https://stackoverflow.com/a/255630/986160) 但更优雅一些。这本字典也是可枚举的,您可以从整数到字符串来回切换。此外,除了此类之外,您的代码库中不必有任何字符串。

                                using System;
                                using System.Collections.Generic;
                                using System.Linq;
                                using System.Text;
                                using System.Collections;
                                
                                namespace MyApp.Dictionaries
                                {
                                
                                    class BiDictionary<TFirst, TSecond> : IEnumerable
                                    {
                                        IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
                                        IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();
                                
                                        public void Add(TFirst first, TSecond second)
                                        {
                                            firstToSecond.Add(first, second);
                                            secondToFirst.Add(second, first);
                                        }
                                
                                        public TSecond this[TFirst first]
                                        {
                                            get { return GetByFirst(first); }
                                        }
                                
                                        public TFirst this[TSecond second]
                                        {
                                            get { return GetBySecond(second); }
                                        }
                                
                                        public TSecond GetByFirst(TFirst first)
                                        {
                                            return firstToSecond[first];
                                        }
                                
                                        public TFirst GetBySecond(TSecond second)
                                        {
                                            return secondToFirst[second];
                                        }
                                
                                        public IEnumerator GetEnumerator()
                                        {
                                            return GetFirstEnumerator();
                                        }
                                
                                        public IEnumerator GetFirstEnumerator()
                                        {
                                            return firstToSecond.GetEnumerator();
                                        }
                                
                                        public IEnumerator GetSecondEnumerator()
                                        {
                                            return secondToFirst.GetEnumerator();
                                        }
                                    }
                                }
                                

                                【讨论】:

                                  【解决方案30】:

                                  对于较大的字符串枚举集,列出的示例可能会令人厌烦。如果你想要一个状态码列表,或者其他基于字符串的枚举列表,属性系统使用起来很烦人,而带有自身实例的静态类配置起来很烦人。对于我自己的解决方案,我使用 T4 模板来更轻松地使用字符串支持的枚举。结果类似于 HttpMethod 类的工作方式。

                                  你可以这样使用它:

                                      string statusCode = ResponseStatusCode.SUCCESS; // Automatically converts to string when needed
                                      ResponseStatusCode codeByValueOf = ResponseStatusCode.ValueOf(statusCode); // Returns null if not found
                                  
                                      // Implements TypeConverter so you can use it with string conversion methods.
                                      var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(ResponseStatusCode));
                                      ResponseStatusCode code = (ResponseStatusCode) converter.ConvertFromInvariantString(statusCode);
                                  
                                      // You can get a full list of the values
                                      bool canIterateOverValues = ResponseStatusCode.Values.Any(); 
                                  
                                      // Comparisons are by value of the "Name" property. Not by memory pointer location.
                                      bool implementsByValueEqualsEqualsOperator = "SUCCESS" == ResponseStatusCode.SUCCESS; 
                                  

                                  您从 Enum.tt 文件开始。

                                  <#@ include file="StringEnum.ttinclude" #>
                                  
                                  
                                  <#+
                                  public static class Configuration
                                  {
                                      public static readonly string Namespace = "YourName.Space";
                                      public static readonly string EnumName = "ResponseStatusCode";
                                      public static readonly bool IncludeComments = true;
                                  
                                      public static readonly object Nodes = new
                                      {
                                          SUCCESS = "The response was successful.",
                                          NON_SUCCESS = "The request was not successful.",
                                          RESOURCE_IS_DISCONTINUED = "The resource requested has been discontinued and can no longer be accessed."
                                      };
                                  }
                                  #>
                                  

                                  然后,添加 StringEnum.ttinclude 文件。

                                  <#@ template debug="false" hostspecific="false" language="C#" #>
                                  <#@ assembly name="System.Core" #>
                                  <#@ import namespace="System" #>
                                  <#@ import namespace="System.Linq" #>
                                  <#@ import namespace="System.Text" #>
                                  <#@ import namespace="System.Reflection" #>
                                  <#@ import namespace="System.Collections.Generic" #>
                                  <#@ output extension=".cs" #>
                                  <#@ CleanupBehavior processor="T4VSHost" CleanupAfterProcessingtemplate="true" #>
                                  
                                  //------------------------------------------------------------------------------
                                  // <auto-generated>
                                  //     This code was generated by a tool.
                                  //
                                  //     Changes to this file may cause incorrect behavior and will be lost if
                                  //     the code is regenerated.
                                  // </auto-generated>
                                  //------------------------------------------------------------------------------
                                  
                                  using System;
                                  using System.Linq;
                                  using System.Collections.Generic;
                                  using System.ComponentModel;
                                  using System.Globalization;
                                  
                                  namespace <#= Configuration.Namespace #>
                                  {
                                      /// <summary>
                                      /// TypeConverter implementations allow you to use features like string.ToNullable(T).
                                      /// </summary>
                                      public class <#= Configuration.EnumName #>TypeConverter : TypeConverter
                                      {
                                          public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
                                          {
                                              return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
                                          }
                                  
                                          public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
                                          {
                                              var casted = value as string;
                                  
                                              if (casted != null)
                                              {
                                                  var result = <#= Configuration.EnumName #>.ValueOf(casted);
                                                  if (result != null)
                                                  {
                                                      return result;
                                                  }
                                              }
                                  
                                              return base.ConvertFrom(context, culture, value);
                                          }
                                  
                                          public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
                                          {
                                              var casted = value as <#= Configuration.EnumName #>;
                                              if (casted != null && destinationType == typeof(string))
                                              {
                                                  return casted.ToString();
                                              }
                                  
                                              return base.ConvertTo(context, culture, value, destinationType);
                                          }
                                      }
                                  
                                      [TypeConverter(typeof(<#= Configuration.EnumName #>TypeConverter))]
                                      public class <#= Configuration.EnumName #> : IEquatable<<#= Configuration.EnumName #>>
                                      {
                                  //---------------------------------------------------------------------------------------------------
                                  // V A L U E S _ L I S T
                                  //---------------------------------------------------------------------------------------------------
                                  <# Write(Helpers.PrintEnumProperties(Configuration.Nodes)); #>
                                  
                                          private static List<<#= Configuration.EnumName #>> _list { get; set; } = null;
                                          public static List<<#= Configuration.EnumName #>> ToList()
                                          {
                                              if (_list == null)
                                              {
                                                  _list = typeof(<#= Configuration.EnumName #>).GetFields().Where(x => x.IsStatic && x.IsPublic && x.FieldType == typeof(<#= Configuration.EnumName #>))
                                                      .Select(x => x.GetValue(null)).OfType<<#= Configuration.EnumName #>>().ToList();
                                              }
                                  
                                              return _list;
                                          }
                                  
                                          public static List<<#= Configuration.EnumName #>> Values()
                                          {
                                              return ToList();
                                          }
                                  
                                          /// <summary>
                                          /// Returns the enum value based on the matching Name of the enum. Case-insensitive search.
                                          /// </summary>
                                          /// <param name="key"></param>
                                          /// <returns></returns>
                                          public static <#= Configuration.EnumName #> ValueOf(string key)
                                          {
                                              return ToList().FirstOrDefault(x => string.Compare(x.Name, key, true) == 0);
                                          }
                                  
                                  
                                  //---------------------------------------------------------------------------------------------------
                                  // I N S T A N C E _ D E F I N I T I O N
                                  //---------------------------------------------------------------------------------------------------      
                                          public string Name { get; private set; }
                                          public string Description { get; private set; }
                                          public override string ToString() { return this.Name; }
                                  
                                          /// <summary>
                                          /// Implcitly converts to string.
                                          /// </summary>
                                          /// <param name="d"></param>
                                          public static implicit operator string(<#= Configuration.EnumName #> d)
                                          {
                                              return d.ToString();
                                          }
                                  
                                          /// <summary>
                                          /// Compares based on the == method. Handles nulls gracefully.
                                          /// </summary>
                                          /// <param name="a"></param>
                                          /// <param name="b"></param>
                                          /// <returns></returns>
                                          public static bool operator !=(<#= Configuration.EnumName #> a, <#= Configuration.EnumName #> b)
                                          {
                                              return !(a == b);
                                          }
                                  
                                          /// <summary>
                                          /// Compares based on the .Equals method. Handles nulls gracefully.
                                          /// </summary>
                                          /// <param name="a"></param>
                                          /// <param name="b"></param>
                                          /// <returns></returns>
                                          public static bool operator ==(<#= Configuration.EnumName #> a, <#= Configuration.EnumName #> b)
                                          {
                                              return a?.ToString() == b?.ToString();
                                          }
                                  
                                          /// <summary>
                                          /// Compares based on the .ToString() method
                                          /// </summary>
                                          /// <param name="o"></param>
                                          /// <returns></returns>
                                          public override bool Equals(object o)
                                          {
                                              return this.ToString() == o?.ToString();
                                          }
                                  
                                          /// <summary>
                                          /// Compares based on the .ToString() method
                                          /// </summary>
                                          /// <param name="other"></param>
                                          /// <returns></returns>
                                          public bool Equals(<#= Configuration.EnumName #> other)
                                          {
                                              return this.ToString() == other?.ToString();
                                          }
                                  
                                          /// <summary>
                                          /// Compares based on the .Name property
                                          /// </summary>
                                          /// <returns></returns>
                                          public override int GetHashCode()
                                          {
                                              return this.Name.GetHashCode();
                                          }
                                      }
                                  }
                                  
                                  <#+
                                  
                                  public static class Helpers
                                  {
                                          public static string PrintEnumProperties(object nodes)
                                          {
                                              string o = "";
                                              Type nodesTp = Configuration.Nodes.GetType();
                                              PropertyInfo[] props = nodesTp.GetProperties().OrderBy(p => p.Name).ToArray();
                                  
                                              for(int i = 0; i < props.Length; i++)
                                              {
                                                  var prop = props[i];
                                                  if (Configuration.IncludeComments)
                                                  {
                                                      o += "\r\n\r\n";
                                                      o += "\r\n        ///<summary>";
                                                      o += "\r\n        /// "+Helpers.PrintPropertyValue(prop, Configuration.Nodes);
                                                      o += "\r\n        ///</summary>";
                                                  }
                                  
                                                  o += "\r\n        public static readonly "+Configuration.EnumName+" "+prop.Name+ " = new "+Configuration.EnumName+"(){ Name = \""+prop.Name+"\", Description = "+Helpers.PrintPropertyValue(prop, Configuration.Nodes)+ "};";
                                              }
                                  
                                              o += "\r\n\r\n";
                                  
                                              return o;
                                          }
                                  
                                          private static Dictionary<string, string> GetValuesMap()
                                          {
                                              Type nodesTp = Configuration.Nodes.GetType();
                                              PropertyInfo[] props= nodesTp.GetProperties();
                                              var dic = new Dictionary<string,string>();
                                              for(int i = 0; i < props.Length; i++)
                                              {
                                                  var prop = nodesTp.GetProperties()[i];
                                                  dic[prop.Name] = prop.GetValue(Configuration.Nodes).ToString();
                                              }
                                              return dic;
                                          }
                                  
                                          public static string PrintMasterValuesMap(object nodes)
                                          {
                                              Type nodesTp = Configuration.Nodes.GetType();
                                              PropertyInfo[] props= nodesTp.GetProperties();
                                              string o = "        private static readonly Dictionary<string, string> ValuesMap = new Dictionary<string, string>()\r\n        {";
                                              for(int i = 0; i < props.Length; i++)
                                              {
                                                  var prop = nodesTp.GetProperties()[i];
                                                  o += "\r\n            { \""+prop.Name+"\", "+(Helpers.PrintPropertyValue(prop,Configuration.Nodes)+" },");
                                              }
                                              o += ("\r\n        };\r\n");
                                  
                                              return o;
                                          }
                                  
                                  
                                          public static string PrintPropertyValue(PropertyInfo prop, object objInstance)
                                          {
                                              switch(prop.PropertyType.ToString()){
                                                  case "System.Double":
                                                      return prop.GetValue(objInstance).ToString()+"D";
                                                  case "System.Float":
                                                      return prop.GetValue(objInstance).ToString()+"F";
                                                  case "System.Decimal":
                                                      return prop.GetValue(objInstance).ToString()+"M";
                                                  case "System.Long":
                                                      return prop.GetValue(objInstance).ToString()+"L";
                                                  case "System.Boolean":
                                                  case "System.Int16":
                                                  case "System.Int32":
                                                      return prop.GetValue(objInstance).ToString().ToLowerInvariant();
                                                  case "System.String":
                                                      return "\""+prop.GetValue(objInstance)+"\"";
                                              }
                                  
                                              return prop.GetValue(objInstance).ToString();
                                          }
                                  
                                          public static string _ (int numSpaces)
                                          {
                                              string o = "";
                                              for(int i = 0; i < numSpaces; i++){
                                                  o += " ";
                                              }
                                  
                                              return o;
                                          }
                                  }
                                  #>
                                  

                                  最后,重新编译 Enum.tt 文件,输出如下所示:

                                  //------------------------------------------------------------------------------
                                  // <auto-generated>
                                  //     This code was generated by a tool.
                                  //
                                  //     Changes to this file may cause incorrect behavior and will be lost if
                                  //     the code is regenerated.
                                  // </auto-generated>
                                  //------------------------------------------------------------------------------
                                  
                                  using System;
                                  using System.Linq;
                                  using System.Collections.Generic;
                                  
                                  namespace YourName.Space
                                  {
                                      public class ResponseStatusCode
                                      {
                                  //---------------------------------------------------------------------------------------------------
                                  // V A L U E S _ L I S T 
                                  //---------------------------------------------------------------------------------------------------
                                  
                                  
                                  
                                          ///<summary>
                                          /// "The response was successful."
                                          ///</summary>
                                          public static readonly ResponseStatusCode SUCCESS = new ResponseStatusCode(){ Name = "SUCCESS", Description = "The response was successful."};
                                  
                                  
                                          ///<summary>
                                          /// "The request was not successful."
                                          ///</summary>
                                          public static readonly ResponseStatusCode NON_SUCCESS = new ResponseStatusCode(){ Name = "NON_SUCCESS", Description = "The request was not successful."};
                                  
                                  
                                          ///<summary>
                                          /// "The resource requested has been discontinued and can no longer be accessed."
                                          ///</summary>
                                          public static readonly ResponseStatusCode RESOURCE_IS_DISCONTINUED = new ResponseStatusCode(){ Name = "RESOURCE_IS_DISCONTINUED", Description = "The resource requested has been discontinued and can no longer be accessed."};
                                  
                                  
                                          private static List<ResponseStatusCode> _list { get; set; } = null;
                                          public static List<ResponseStatusCode> ToList()
                                          {
                                              if (_list == null)
                                              {
                                                  _list = typeof(ResponseStatusCode).GetFields().Where(x => x.IsStatic && x.IsPublic && x.FieldType == typeof(ResponseStatusCode))
                                                      .Select(x => x.GetValue(null)).OfType<ResponseStatusCode>().ToList();
                                              }
                                  
                                              return _list;
                                          }
                                  
                                          public static List<ResponseStatusCode> Values()
                                          {
                                              return ToList();
                                          }
                                  
                                          /// <summary>
                                          /// Returns the enum value based on the matching Name of the enum. Case-insensitive search.
                                          /// </summary>
                                          /// <param name="key"></param>
                                          /// <returns></returns>
                                          public static ResponseStatusCode ValueOf(string key)
                                          {
                                              return ToList().FirstOrDefault(x => string.Compare(x.Name, key, true) == 0);
                                          }
                                  
                                  
                                  //---------------------------------------------------------------------------------------------------
                                  // I N S T A N C E _ D E F I N I T I O N 
                                  //---------------------------------------------------------------------------------------------------       
                                          public string Name { get; set; }
                                          public string Description { get; set; }
                                          public override string ToString() { return this.Name; }
                                  
                                          /// <summary>
                                          /// Implcitly converts to string.
                                          /// </summary>
                                          /// <param name="d"></param>
                                          public static implicit operator string(ResponseStatusCode d)
                                          {
                                              return d.ToString();
                                          }
                                  
                                          /// <summary>
                                          /// Compares based on the == method. Handles nulls gracefully.
                                          /// </summary>
                                          /// <param name="a"></param>
                                          /// <param name="b"></param>
                                          /// <returns></returns>
                                          public static bool operator !=(ResponseStatusCode a, ResponseStatusCode b)
                                          {
                                              return !(a == b);
                                          }
                                  
                                          /// <summary>
                                          /// Compares based on the .Equals method. Handles nulls gracefully.
                                          /// </summary>
                                          /// <param name="a"></param>
                                          /// <param name="b"></param>
                                          /// <returns></returns>
                                          public static bool operator ==(ResponseStatusCode a, ResponseStatusCode b)
                                          {
                                              return a?.ToString() == b?.ToString();
                                          }
                                  
                                          /// <summary>
                                          /// Compares based on the .ToString() method
                                          /// </summary>
                                          /// <param name="o"></param>
                                          /// <returns></returns>
                                          public override bool Equals(object o)
                                          {
                                              return this.ToString() == o?.ToString();
                                          }
                                  
                                          /// <summary>
                                          /// Compares based on the .Name property
                                          /// </summary>
                                          /// <returns></returns>
                                          public override int GetHashCode()
                                          {
                                              return this.Name.GetHashCode();
                                          }
                                      }
                                  }
                                  

                                  【讨论】:

                                    猜你喜欢
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2023-03-04
                                    • 2023-03-10
                                    • 2017-05-07
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2014-07-23
                                    相关资源
                                    最近更新 更多