【问题标题】:Can my enums have friendly names? [duplicate]我的枚举可以有友好的名字吗? [复制]
【发布时间】:2009-09-12 13:41:32
【问题描述】:

我有以下enum

public enum myEnum
{
    ThisNameWorks, 
    This Name doesn't work
    Neither.does.this;
}

enums 不能使用“友好名称”吗?

【问题讨论】:

  • ThisNameWorks 有什么不友好的地方?
  • 这很“不友好”,因为他可能希望能够在表单或网页上显示枚举名称,但因为没有空格而不能。

标签: c# enums


【解决方案1】:

您可以按照 Yuriy 的建议使用 Description 属性。以下扩展方法可以轻松获取给定枚举值的描述​​:

public static string GetDescription(this Enum value)
{
    Type type = value.GetType();
    string name = Enum.GetName(type, value);
    if (name != null)
    {
        FieldInfo field = type.GetField(name);
        if (field != null)
        {
            DescriptionAttribute attr = 
                   Attribute.GetCustomAttribute(field, 
                     typeof(DescriptionAttribute)) as DescriptionAttribute;
            if (attr != null)
            {
                return attr.Description;
            }
        }
    }
    return null;
}

你可以这样使用它:

public enum MyEnum
{
    [Description("Description for Foo")]
    Foo,
    [Description("Description for Bar")]
    Bar
}

MyEnum x = MyEnum.Foo;
string description = x.GetDescription();

【讨论】:

  • 我计划今晚将对此的支持添加到 UnconstrainedMelody 中。
  • 当帖子不包含他们引用的非默认命名空间时,这很烦人...System.ComponentModelSystem.Reflection
  • @musefan,我从不包括它们,因为它们只会增加噪音。 Visual Studio 只需单击 2 次即可自动添加它们...
  • 这应该被接受为答案,而不是当前接受的答案。
  • @ThomasLevesque 经常匹配多个命名空间(尤其是与描述这样的泛型类)。也不是每个人都在使用 Visual Studio 编写 c#
【解决方案2】:

枚举值名称必须遵循与 C# 中所有标识符相同的命名规则,因此只有名字是正确的。

【讨论】:

  • 您可以使用System.ComponentModel.DataAnnotations 并添加显示属性。 (如:[Display(Name = "This Name doesn't work")]
  • Cas 的评论实际上是当今最常用的解决方案。
  • @CasBloem 不再起作用了。
  • @CasBloem 以防万一有人试图在旧版本的 .NET 中使用 Display,它仅在 .NET 4.5.2 时才受支持。 docs.microsoft.com/en-us/dotnet/api/…
  • Thomas Levesque 的答案应设置为接受的答案。
【解决方案3】:

如果你有以下枚举:

public enum MyEnum {
    First,
    Second,
    Third
}

您可以为MyEnum 声明扩展方法(就像您可以为任何其他类型一样)。我刚刚把它搞砸了:

namespace Extension {
    public static class ExtensionMethods {
        public static string EnumValue(this MyEnum e) {
            switch (e) {
                case MyEnum.First:
                    return "First Friendly Value";
                case MyEnum.Second:
                    return "Second Friendly Value";
                case MyEnum.Third:
                    return "Third Friendly Value";
            }
            return "Horrible Failure!!";
        }
    }
}

使用此扩展方法,以下内容现在是合法的:

Console.WriteLine(MyEnum.First.EnumValue());

希望这会有所帮助!

【讨论】:

  • 请记住,switch 语句实际上是复杂度为 O(n) 的线性搜索——最坏的情况需要与枚举中的非常值进行比较。
  • @Roy - 这并不总是正确的,有时(确切地说是基于实现细节)C# 编译器会发出字典查找,请参阅:stackoverflow.com/questions/3366376/…
  • @John - 谢谢你的信息,我不知道! ...我认为 C++ 编译器发出的程序集与 if-elseif-elseif-etc 链本质上相同,这仍然是正确的。我得查一下。
  • 罗伊,我在这里可能是错的,但我的印象是那不是真的。虽然 C/C++ 编译器可能会发出 if/else if 链,但我认为如果编译器认为合适,它也可以发出跳转表。例如,如果你关闭一个字节值,我认为它很可能只是发出一个跳转表。
  • Nice Slick Solution,不需要属性和排序
【解决方案4】:

不,但您可以使用DescriptionAttribute 来完成您要查找的内容。

【讨论】:

    【解决方案5】:

    您可以使用Description 属性来获取该友好名称。您可以使用以下代码:

    public static string ToStringEnums(Enum en)
    {
        Type type = en.GetType();
    
        MemberInfo[] memInfo = type.GetMember(en.ToString());
        if (memInfo != null && memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
            if (attrs != null && attrs.Length > 0)
                return ((DescriptionAttribute)attrs[0]).Description;
        }
        return en.ToString();
    }
    

    您希望使用此方法的示例:当您的枚举值为 EncryptionProviderType 并且您希望 enumVar.Tostring() 返回“加密提供程序类型”时。

    先决条件:所有枚举成员都应使用属性[Description("String to be returned by Tostring()")]

    示例枚举:

    enum ExampleEnum
    {
        [Description("One is one")]
        ValueOne = 1,
        [Description("Two is two")]
        ValueTow = 2
    }
    

    在你的课堂上,你会这样使用它:

    ExampleEnum enumVar = ExampleEnum.ValueOne;
    Console.WriteLine(ToStringEnums(enumVar));
    

    【讨论】:

    • 很好,如何从描述中获取枚举?
    【解决方案6】:

    这个技巧的一个问题是描述属性不能被本地化。我确实喜欢 Sacha Barber 的一项技术,他创建了自己的 Description 属性版本,该属性将从相应的资源管理器中获取值。

    http://www.codeproject.com/KB/WPF/FriendlyEnums.aspx

    虽然本文围绕 WPF 开发人员在绑定到枚举时通常面临的问题,但您可以直接跳转到他创建 LocalizableDescriptionAttribute 的部分。

    【讨论】:

      【解决方案7】:

      已经发布了一些很棒的解决方案。当我遇到这个问题时,我想双向:将枚举转换为描述,将匹配描述的字符串转换为枚举。

      我有两种变体,慢速和快速。两者都从枚举转换为字符串和从字符串转换为枚举。我的问题是我有这样的枚举,其中有些元素需要属性,而有些则不需要。我不想将属性放在不需要它们的元素上。我目前总共有大约一百个:

      public enum POS
      {   
          CC, //  Coordinating conjunction
          CD, //  Cardinal Number
          DT, //  Determiner
          EX, //  Existential there
          FW, //  Foreign Word
          IN, //  Preposision or subordinating conjunction
          JJ, //  Adjective
          [System.ComponentModel.Description("WP$")]
          WPDollar, //$   Possessive wh-pronoun
          WRB, //     Wh-adverb
          [System.ComponentModel.Description("#")]
          Hash,
          [System.ComponentModel.Description("$")]
          Dollar,
          [System.ComponentModel.Description("''")]
          DoubleTick,
          [System.ComponentModel.Description("(")]
          LeftParenth,
          [System.ComponentModel.Description(")")]
          RightParenth,
          [System.ComponentModel.Description(",")]
          Comma,
          [System.ComponentModel.Description(".")]
          Period,
          [System.ComponentModel.Description(":")]
          Colon,
          [System.ComponentModel.Description("``")]
          DoubleBackTick,
          };
      

      处理这个问题的第一种方法很慢,并且基于我在这里和网络上看到的建议。这很慢,因为我们要针对每次转化进行反思:

      using System;
      using System.Collections.Generic;
      namespace CustomExtensions
      {
      
      /// <summary>
      /// uses extension methods to convert enums with hypens in their names to underscore and other variants
      public static class EnumExtensions
      {
          /// <summary>
          /// Gets the description string, if available. Otherwise returns the name of the enum field
          /// LthWrapper.POS.Dollar.GetString() yields "$", an impossible control character for enums
          /// </summary>
          /// <param name="value"></param>
          /// <returns></returns>
          public static string GetStringSlow(this Enum value)
          {
              Type type = value.GetType();
              string name = Enum.GetName(type, value);
              if (name != null)
              {
                  System.Reflection.FieldInfo field = type.GetField(name);
                  if (field != null)
                  {
                      System.ComponentModel.DescriptionAttribute attr =
                             Attribute.GetCustomAttribute(field,
                               typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute;
                      if (attr != null)
                      {
                          //return the description if we have it
                          name = attr.Description; 
                      }
                  }
              }
              return name;
          }
      
          /// <summary>
          /// Converts a string to an enum field using the string first; if that fails, tries to find a description
          /// attribute that matches. 
          /// "$".ToEnum<LthWrapper.POS>() yields POS.Dollar
          /// </summary>
          /// <typeparam name="T"></typeparam>
          /// <param name="value"></param>
          /// <returns></returns>
          public static T ToEnumSlow<T>(this string value) //, T defaultValue)
          {
              T theEnum = default(T);
      
              Type enumType = typeof(T);
      
              //check and see if the value is a non attribute value
              try
              {
                  theEnum = (T)Enum.Parse(enumType, value);
              }
              catch (System.ArgumentException e)
              {
                  bool found = false;
                  foreach (T enumValue in Enum.GetValues(enumType))
                  {
                      System.Reflection.FieldInfo field = enumType.GetField(enumValue.ToString());
      
                      System.ComponentModel.DescriptionAttribute attr =
                                 Attribute.GetCustomAttribute(field,
                                   typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute;
      
                      if (attr != null && attr.Description.Equals(value))
                      {
                          theEnum = enumValue;
                          found = true;
                          break;
      
                      }
                  }
                  if( !found )
                      throw new ArgumentException("Cannot convert " + value + " to " + enumType.ToString());
              }
      
              return theEnum;
          }
      }
      }
      

      问题在于您每次都在进行反思。我还没有衡量这样做对性能的影响,但这似乎令人担忧。更糟糕的是,我们重复计算这些昂贵的转换,而不缓存它们。

      相反,我们可以使用静态构造函数来使用此转换信息填充一些字典,然后在需要时查找此信息。显然静态类(扩展方法需要)可以有构造函数和字段:)

      using System;
      using System.Collections.Generic;
      namespace CustomExtensions
      {
      
      /// <summary>
      /// uses extension methods to convert enums with hypens in their names to underscore and other variants
      /// I'm not sure this is a good idea. While it makes that section of the code much much nicer to maintain, it 
      /// also incurs a performance hit via reflection. To circumvent this, I've added a dictionary so all the lookup can be done once at 
      /// load time. It requires that all enums involved in this extension are in this assembly.
      /// </summary>
      public static class EnumExtensions
      {
          //To avoid collisions, every Enum type has its own hash table
          private static readonly Dictionary<Type, Dictionary<object,string>> enumToStringDictionary = new Dictionary<Type,Dictionary<object,string>>();
          private static readonly Dictionary<Type, Dictionary<string, object>> stringToEnumDictionary = new Dictionary<Type, Dictionary<string, object>>();
      
          static EnumExtensions()
          {
              //let's collect the enums we care about
              List<Type> enumTypeList = new List<Type>();
      
              //probe this assembly for all enums
              System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
              Type[] exportedTypes = assembly.GetExportedTypes();
      
              foreach (Type type in exportedTypes)
              {
                  if (type.IsEnum)
                      enumTypeList.Add(type);
              }
      
              //for each enum in our list, populate the appropriate dictionaries
              foreach (Type type in enumTypeList)
              {
                  //add dictionaries for this type
                  EnumExtensions.enumToStringDictionary.Add(type, new Dictionary<object,string>() );
                  EnumExtensions.stringToEnumDictionary.Add(type, new Dictionary<string,object>() );
      
                  Array values = Enum.GetValues(type);
      
                  //its ok to manipulate 'value' as object, since when we convert we're given the type to cast to
                  foreach (object value in values)
                  {
                      System.Reflection.FieldInfo fieldInfo = type.GetField(value.ToString());
      
                      //check for an attribute 
                      System.ComponentModel.DescriptionAttribute attribute =
                             Attribute.GetCustomAttribute(fieldInfo,
                               typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute;
      
                      //populate our dictionaries
                      if (attribute != null)
                      {
                          EnumExtensions.enumToStringDictionary[type].Add(value, attribute.Description);
                          EnumExtensions.stringToEnumDictionary[type].Add(attribute.Description, value);
                      }
                      else
                      {
                          EnumExtensions.enumToStringDictionary[type].Add(value, value.ToString());
                          EnumExtensions.stringToEnumDictionary[type].Add(value.ToString(), value);
                      }
                  }
              }
          }
      
          public static string GetString(this Enum value)
          {
              Type type = value.GetType();
              string aString = EnumExtensions.enumToStringDictionary[type][value];
              return aString; 
          }
      
          public static T ToEnum<T>(this string value)
          {
              Type type = typeof(T);
              T theEnum = (T)EnumExtensions.stringToEnumDictionary[type][value];
              return theEnum;
          }
       }
      }
      

      看看现在的转换方法有多紧。我能想到的唯一缺陷是,这要求所有转换后的枚举都在当前程序集中。另外,我只关心导出的枚举,但如果你愿意,你可以改变它。

      这是调用方法的方法

       string x = LthWrapper.POS.Dollar.GetString();
       LthWrapper.POS y = "PRP$".ToEnum<LthWrapper.POS>();
      

      【讨论】:

      • 立即缓存所有内容不利于内存。按需做会更好。我建议使用EnumExtensions&lt;T&gt; 之类的东西来弥补支持。
      【解决方案8】:
      public enum myEnum
      {
               ThisNameWorks, 
               This_Name_can_be_used_instead,
      
      }
      

      【讨论】:

      • 可能,但应该避免。不遵循 MS 命名准则。我认为它只适用于老 C 开发人员或其他东西:p
      • 我也尝试过做蛇形外壳,然后只做一个 .Replace 来获取您的用户友好名称。
      【解决方案9】:

      在阅读了许多有关此主题的资源(包括 StackOverFlow)后,我发现并非所有解决方案都能正常工作。以下是我们解决此问题的尝试。

      基本上,如果存在,我们会从 DescriptionAttribute 中获取枚举的友好名称。
      如果不是,我们使用 RegEx 来确定 Enum 名称中的单词并添加空格。

      下一个版本,我们将使用另一个属性来标记我们是否可以/应该从可本地化的资源文件中获取友好名称。

      以下是测试用例。如果您还有其他未通过的测试用例,请报告。

      public static class EnumHelper
      {
          public static string ToDescription(Enum value)
          {
              if (value == null)
              {
                  return string.Empty;
              }
      
              if (!Enum.IsDefined(value.GetType(), value))
              {
                  return string.Empty;
              }
      
              FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
              if (fieldInfo != null)
              {
                  DescriptionAttribute[] attributes =
                      fieldInfo.GetCustomAttributes(typeof (DescriptionAttribute), false) as DescriptionAttribute[];
                  if (attributes != null && attributes.Length > 0)
                  {
                      return attributes[0].Description;
                  }
              }
      
              return StringHelper.ToFriendlyName(value.ToString());
          }
      }
      
      public static class StringHelper
      {
          public static bool IsNullOrWhiteSpace(string value)
          {
              return value == null || string.IsNullOrEmpty(value.Trim());
          }
      
          public static string ToFriendlyName(string value)
          {
              if (value == null) return string.Empty;
              if (value.Trim().Length == 0) return string.Empty;
      
              string result = value;
      
              result = string.Concat(result.Substring(0, 1).ToUpperInvariant(), result.Substring(1, result.Length - 1));
      
              const string pattern = @"([A-Z]+(?![a-z])|\d+|[A-Z][a-z]+|(?![A-Z])[a-z]+)+";
      
              List<string> words = new List<string>();
              Match match = Regex.Match(result, pattern);
              if (match.Success)
              {
                  Group group = match.Groups[1];
                  foreach (Capture capture in group.Captures)
                  {
                      words.Add(capture.Value);
                  }
              }
      
              return string.Join(" ", words.ToArray());
          }
      }
      
      
          [TestMethod]
          public void TestFriendlyName()
          {
              string[][] cases =
                  {
                      new string[] {null, string.Empty},
                      new string[] {string.Empty, string.Empty},
                      new string[] {" ", string.Empty}, 
                      new string[] {"A", "A"},
                      new string[] {"z", "Z"},
      
                      new string[] {"Pascal", "Pascal"},
                      new string[] {"camel", "Camel"},
      
                      new string[] {"PascalCase", "Pascal Case"}, 
                      new string[] {"ABCPascal", "ABC Pascal"}, 
                      new string[] {"PascalABC", "Pascal ABC"}, 
                      new string[] {"Pascal123", "Pascal 123"}, 
                      new string[] {"Pascal123ABC", "Pascal 123 ABC"}, 
                      new string[] {"PascalABC123", "Pascal ABC 123"}, 
                      new string[] {"123Pascal", "123 Pascal"}, 
                      new string[] {"123ABCPascal", "123 ABC Pascal"}, 
                      new string[] {"ABC123Pascal", "ABC 123 Pascal"}, 
      
                      new string[] {"camelCase", "Camel Case"}, 
                      new string[] {"camelABC", "Camel ABC"}, 
                      new string[] {"camel123", "Camel 123"}, 
                  };
      
              foreach (string[] givens in cases)
              {
                  string input = givens[0];
                  string expected = givens[1];
                  string output = StringHelper.ToFriendlyName(input);
      
                  Assert.AreEqual(expected, output);
              }
          }
      }
      

      【讨论】:

        【解决方案10】:

        它们遵循与变量名称相同的命名规则。 因此它们不应包含空格。

        你的建议无论如何都是非常糟糕的做法。

        【讨论】:

          【解决方案11】:

          枚举名称与普通变量名称遵循相同的规则,即名称中间没有空格或点...虽然我仍然认为第一个相当友好...

          【讨论】:

            【解决方案12】:

            这是一个糟糕的主意,但它确实有效。

                public enum myEnum
            {
                ThisNameWorks,
                ThisNameDoesntWork149141331,// This Name doesn't work
                NeitherDoesThis1849204824// Neither.does.this;
            }
            
            class Program
            {
                private static unsafe void ChangeString(string original, string replacement)
                {
                    if (original.Length < replacement.Length)
                        throw new ArgumentException();
            
                    fixed (char* pDst = original)
                    fixed (char* pSrc = replacement)
                    {
                        // Update the length of the original string
                        int* lenPtr = (int*)pDst;
                        lenPtr[-1] = replacement.Length;
            
                        // Copy the characters
                        for (int i = 0; i < replacement.Length; i++)
                            pDst[i] = pSrc[i];
                    }
                }
            
                public static unsafe void Initialize()
                {
                    ChangeString(myEnum.ThisNameDoesntWork149141331.ToString(), "This Name doesn't work");
                    ChangeString(myEnum.NeitherDoesThis1849204824.ToString(), "Neither.does.this");
                }
            
                static void Main(string[] args)
                {
                    Console.WriteLine(myEnum.ThisNameWorks);
                    Console.WriteLine(myEnum.ThisNameDoesntWork149141331);
                    Console.WriteLine(myEnum.NeitherDoesThis1849204824);
            
                    Initialize();
            
                    Console.WriteLine(myEnum.ThisNameWorks);
                    Console.WriteLine(myEnum.ThisNameDoesntWork149141331);
                    Console.WriteLine(myEnum.NeitherDoesThis1849204824);
                }
            

            要求

            1. 您的枚举名称的字符数必须与您希望的字符串相同或更多。

            2. 你的枚举名称不应该在任何地方重复,以防字符串实习把事情搞砸

            为什么这是一个坏主意(几个原因)

            1. 您的枚举名称因要求而变得难看

            2. 它依赖于你足够早地调用初始化方法

            3. 不安全的指针

            4. 如果字符串的内部格式发生变化,例如如果长度字段被移动,你就完蛋了

            5. 如果 Enum.ToString() 曾经更改为只返回一个副本,那你就完蛋了

            6. Raymond Chen 会抱怨您使用未记录的功能,以及 CLR 团队无法在他的下一个 .NET 周内进行优化以将运行时间缩短 50% 是您的错。

            【讨论】:

              【解决方案13】:

              我想你想向用户显示你的枚举值,因此,你希望他们有一些友好的名字。

              这是我的建议:

              使用枚举类型模式。虽然实现起来要花点功夫,但真的很值得。

              public class MyEnum
              {  
                  public static readonly MyEnum Enum1=new MyEnum("This will work",1);
                  public static readonly MyEnum Enum2=new MyEnum("This.will.work.either",2);
                  public static readonly MyEnum[] All=new []{Enum1,Enum2};
                  private MyEnum(string name,int value)
                  {
                      Name=name;
                      Value=value;
                  }
              
                  public string Name{get;set;}
                  public int Value{get;set;}
              
                  public override string ToString()
                  {
                      return Name;    
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2012-08-20
                • 2013-12-21
                • 2010-10-03
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多