枚举的 ToString 方法如何返回易于理解的字符串?

366

我的枚举包含以下值:

private enum PublishStatusses{
    NotCompleted,
    Completed,
    Error
};

我希望能以用户友好的方式输出这些值。
但我不需要再将字符串转换回值。


1
可能是C#字符串枚举的重复问题。 - nawfal
27个回答

6

不要使用枚举,而是使用静态类。

替换

private enum PublishStatuses{
    NotCompleted,
    Completed,
    Error
};

使用

private static class PublishStatuses{
    public static readonly string NotCompleted = "Not Completed";
    public static readonly string Completed = "Completed";
    public static readonly string Error = "Error";
};

它将被这样使用

PublishStatuses.NotCompleted; // "Not Completed"

使用“扩展方法”解决方案时出现问题:

私有枚举通常在另一个类内部使用。扩展方法解决方案在此处无效,因为它必须在自己的类中。这个解决方案可以是私有的,并嵌入到另一个类中。


当将 Enums 传递给方法时,将失去其值,因为所有方法签名都需要 string,因此可以传递任何内容。这会引入需要额外验证并降低通用可读性的需求。 - CajunCoding

5
关于Ray Booysen的问题,代码存在一个错误:枚举类型 ToString 方法的用户友好字符串
您需要考虑枚举值上的多个属性。
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 && attrs.Where(t => t.GetType() == typeof(DescriptionAttribute)).FirstOrDefault() != null)
            {
                //Pull out the description value
                return ((DescriptionAttribute)attrs.Where(t=>t.GetType() == typeof(DescriptionAttribute)).FirstOrDefault()).Description;
            }
        }
        //If we have no description attribute, just return the ToString of the enum
        return enumerationValue.ToString();

6
省略检查多个描述属性是故意的。如果枚举有两个描述属性,并且要使用它们生成一个描述,我认为这是一个特殊情况。我认为真正的错误是我没有使用Single()抛出异常。否则整个方法签名就毫无意义。GetDescription()? 哪个描述?总和? - Ray Booysen

4

以下是建议摘要和示例:

namespace EnumExtensions {

using System;
using System.Reflection;

public class TextAttribute : Attribute {
   public string Text;
   public TextAttribute( string text ) {
      Text = text;
   }//ctor
}// class TextAttribute

public static class EnumExtender {

public static string ToText( this Enum enumeration ) {

   MemberInfo[] memberInfo = enumeration.GetType().GetMember( enumeration.ToString() );

   if ( memberInfo != null && memberInfo.Length > 0 ) {

      object[] attributes = memberInfo[ 0 ].GetCustomAttributes( typeof(TextAttribute),  false );

      if ( attributes != null && attributes.Length > 0 ) {
         return ( (TextAttribute)attributes[ 0 ] ).Text;
      }

   }//if

   return enumeration.ToString();

}//ToText

}//class EnumExtender

}//namespace

使用方法:

using System;
using EnumExtensions;

class Program {

public enum Appearance {

  [Text( "left-handed" ) ]
  Left,

  [Text( "right-handed" ) ]
  Right,

}//enum

static void Main( string[] args ) {

   var appearance = Appearance.Left;
   Console.WriteLine( appearance.ToText() );

}//Main

}//class

1
在C#中有一个[Description("")]属性,为什么不使用它呢? - Stefan Koenen
当然,使用 [Description("")] 是一种好方法。但是我希望示例完整。 - underscore

3

我晚了7年才参加这个派对 :-) 但我相信这个主题经常被访问。
所以,我想给咖啡加点糖:

那么,“F”格式字符串指示符呢?

PublishStatusses[] ps = Enum.GetValues<PublishStatusses>();
ps.ToList().ForEach(c => Console.Write($"{c:F} "));

不需要任何显式的函数调用。

事实上,甚至不需要任何格式说明符。 如果将变量赋值给字符串,在ToString()函数中完成工作:

string foo = PublishStatusses.Error.ToString(); // or ToString("F")

如果要在CamelCase字符串中单词之间插入空格,您可以使用正则表达式:

Regex.Replace(foo, "(\\B[A-Z])", " $1")

1
小心使用F格式字符串。它主要用于Flags枚举,并且如果值可以由多个枚举成员的总和表示,即使没有Flags属性,它也可能给出意外的结果。G格式字符串只会给出匹配成员的名称,如果未定义,则给出数值,并且在Flags枚举中仍然按预期工作(显示所有设置的标志)。请参阅:https://learn.microsoft.com/en-us/dotnet/standard/base-types/enumeration-format-strings#f-or-f 以了解F可能出现意外行为的示例(例如DayOfWeek)。 - undefined
有趣,我不知道这个。 - undefined

3

我恰巧是VB.NET的粉丝,下面是我的版本,结合了DescriptionAttribute方法和扩展方法。首先,这是结果:

Imports System.ComponentModel ' For <Description>

Module Module1
  ''' <summary>
  ''' An Enum type with three values and descriptions
  ''' </summary>
  Public Enum EnumType
    <Description("One")>
    V1 = 1

    ' This one has no description
    V2 = 2

    <Description("Three")>
    V3 = 3
  End Enum

  Sub Main()
    ' Description method is an extension in EnumExtensions
    For Each v As EnumType In [Enum].GetValues(GetType(EnumType))
      Console.WriteLine("Enum {0} has value {1} and description {2}",
        v,
        CInt(v),
        v.Description
      )
    Next
    ' Output:
    ' Enum V1 has value 1 and description One
    ' Enum V2 has value 2 and description V2
    ' Enum V3 has value 3 and description Three
  End Sub
End Module

基础内容:一个名为EnumType的枚举,包括三个值V1、V2和V3。在Sub Main ()中,通过Console.WriteLine调用时,最后一个参数是简单的v.Description。对于V1,返回“One”,对于V2,则返回“V2”,对于V3,则返回“Three”。实际上,这个Description方法是一个扩展方法,在另一个叫做EnumExtensions的模块中定义:

Option Strict On
Option Explicit On
Option Infer Off

Imports System.Runtime.CompilerServices
Imports System.Reflection
Imports System.ComponentModel

Module EnumExtensions
  Private _Descriptions As New Dictionary(Of String, String)

  ''' <summary>
  ''' This extension method adds a Description method
  ''' to all enum members. The result of the method is the
  ''' value of the Description attribute if present, else
  ''' the normal ToString() representation of the enum value.
  ''' </summary>
  <Extension>
  Public Function Description(e As [Enum]) As String
    ' Get the type of the enum
    Dim enumType As Type = e.GetType()
    ' Get the name of the enum value
    Dim name As String = e.ToString()

    ' Construct a full name for this enum value
    Dim fullName As String = enumType.FullName + "." + name

    ' See if we have looked it up earlier
    Dim enumDescription As String = Nothing
    If _Descriptions.TryGetValue(fullName, enumDescription) Then
      ' Yes we have - return previous value
      Return enumDescription
    End If

    ' Find the value of the Description attribute on this enum value
    Dim members As MemberInfo() = enumType.GetMember(name)
    If members IsNot Nothing AndAlso members.Length > 0 Then
      Dim descriptions() As Object = members(0).GetCustomAttributes(GetType(DescriptionAttribute), False)
      If descriptions IsNot Nothing AndAlso descriptions.Length > 0 Then
        ' Set name to description found
        name = DirectCast(descriptions(0), DescriptionAttribute).Description
      End If
    End If

    ' Save the name in the dictionary:
    _Descriptions.Add(fullName, name)

    ' Return the name
    Return name
  End Function
End Module

因为使用 Reflection 查找描述属性很慢,所以这些查找也会缓存在一个私有 Dictionary 中,该字典在需要时填充。
(抱歉提供的是 VB.NET 解决方案 - 将其翻译成 C# 应该相对简单,而且我的 C# 在新主题(如扩展)上有点生疏)

备选的语言解决方案肯定对某些人有帮助。然而,我只想指出,对于现代时代的读者来说,这个答案已经过时了,至少在我发表评论的时候已经过去了10年。在现代版本的.NET中,ToString(string)的内部实现已经避免了装箱并且缓存了结果,所以手动实现已经不再必要,而且可能比内置方法的性能差。 - undefined

3

更加简洁的概述:

using System;
using System.Reflection;

public class TextAttribute : Attribute
{
    public string Text;
    public TextAttribute(string text)
    {
        Text = text;
    }
}  

public static class EnumExtender
{
    public static string ToText(this Enum enumeration)
    {
        var memberInfo = enumeration.GetType().GetMember(enumeration.ToString());
        if (memberInfo.Length <= 0) return enumeration.ToString();

        var attributes = memberInfo[0].GetCustomAttributes(typeof(TextAttribute), false);
        return attributes.Length > 0 ? ((TextAttribute)attributes[0]).Text : enumeration.ToString();
    }
}

与underscore描述的用法相同。

3

使用 Enum.GetName

从上面的链接中...

using System;

public class GetNameTest {
    enum Colors { Red, Green, Blue, Yellow };
    enum Styles { Plaid, Striped, Tartan, Corduroy };

    public static void Main() {

        Console.WriteLine("The 4th value of the Colors Enum is {0}", Enum.GetName(typeof(Colors), 3));
        Console.WriteLine("The 4th value of the Styles Enum is {0}", Enum.GetName(typeof(Styles), 3));
    }
}
// The example displays the following output:
//       The 4th value of the Colors Enum is Yellow
//       The 4th value of the Styles Enum is Corduroy

1
如果您想要一个不同于枚举名称的名称,例如带有空格的名称,该怎么办? - Stealth Rabbi
嗨@StealthRabbi,我认为这个问题已经在这篇帖子的另一个回复中得到了解答--> https://dev59.com/knRB5IYBdhLWcg3w4bEo#479417 - J.M.H
@J.M.H 如果您想要本地化的名称呢 :> - Whitebrim
如果你只是想获取枚举值的名称,为什么不直接调用它的ToString("G")方法,而不是通过装箱和反射来使用Enum.GetName方法呢?微软建议避免直接使用System.Enum,因为它总是会进行装箱操作,并且自.NET 6以来(甚至可能更早),枚举类型的ToString("G")方法的内部实现是高效的,它会缓存结果,所以反射只会在每个枚举值上发生一次。ToString(string)方法在所有的.NET版本中都存在,从1.1版本的Framework到1.0版本的Standard,因此它是存在的,不会因为缺少值而抛出异常。 - undefined
@Whitebrim,那么枚举并不是一个合适的结构。本地化不应该与低级代码实现细节绑定在一起。如果你需要本地化字符串,可以使用更正式的类型或其他更合适的结构。至少在 .net 中,有一个地方做得很失败,那就是 DayOfWeek 枚举。它有 7 个成员,全部使用英文名称,并且将星期日作为 0 值。并非所有文化都有一周 7 天,也不是所有有 7 天的文化都以星期日作为一周的开始。糟糕。 - undefined

2

2
使用“g”(或“G”)格式字符串是枚举的默认行为,正如此页面上的备注所示(https://learn.microsoft.com/en-us/dotnet/api/system.enum.tostring?view=netframework-4.8#System_Enum_ToString)。 - Mike Christiansen
1
这是一个链接,指向允许在枚举类型中使用的格式化字符串。(https://learn.microsoft.com/en-us/dotnet/standard/base-types/enumeration-format-strings) - Mike Christiansen
这并没有回答原始问题,原始问题是提供一个字符串,该字符串与枚举成员名称不匹配,以便进行用户友好的显示。最常接受的方法,也是微软自己有示例的方法,是使用扩展方法为枚举提供自定义格式化,因为System.Enum实际上不使用ToString(IFormatProvider)方法中的IFormatProvider。最有效的方法往往是使用一个扩展方法,其中包含一个switch表达式,根据枚举值返回字符串,并根据您可能想要添加的其他行为进行扩展。 - undefined

2
如果你无论如何都使用Newtonsoft,你可以按照以下方式进行操作:
// Enum

[Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))]
public enum MyEnum
{
   [EnumMember(Value = "User friendly value")]
   SomeValue=0,
}

// Usage as extension method

public static string ToDefaultString(this Enum enumValue)
{
   return JsonConvert.SerializeObject(enumValue).Replace("\"", "");
}



1

这是对Ray Booysen代码的更新,它使用通用的GetCustomAttributes方法和LINQ使事情变得更加整洁。

    /// <summary>
    /// Gets the value of the <see cref="T:System.ComponentModel.DescriptionAttribute"/> on an struct, including enums.  
    /// </summary>
    /// <typeparam name="T">The type of the struct.</typeparam>
    /// <param name="enumerationValue">A value of type <see cref="T:System.Enum"/></param>
    /// <returns>If the struct has a Description attribute, this method returns the description.  Otherwise it just calls ToString() on the struct.</returns>
    /// <remarks>Based on https://dev59.com/knRB5IYBdhLWcg3w4bEo#479417, but useful for any struct.</remarks>
    public static string GetDescription<T>(this T enumerationValue) where T : struct
    {
        return enumerationValue.GetType().GetMember(enumerationValue.ToString())
                .SelectMany(mi => mi.GetCustomAttributes<DescriptionAttribute>(false),
                    (mi, ca) => ca.Description)
                .FirstOrDefault() ?? enumerationValue.ToString();
    }   

不明白为什么你需要它是通用的? 如果你要使用反射? - Lee Louviere
主要是为了避免在将结构体(值类型)作为参数传递时出现装箱。 - Richard Anthony Freeman-Hein
1
请使用 typeof(T) 替代 numerationValue.GetType()。 - Slava
1
巨大的一行改进,比已接受的答案更好,而且不会失去可读性(可能因人而异)。是的,使用typeof(T)。 - TonyG

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