关于c#:Enum ToString,用户友好的字符串

Enum ToString with user friendly strings

我的枚举由以下值组成:

1
2
3
4
5
private enum PublishStatusses{
    NotCompleted,
    Completed,
    Error
};

不过,我希望能够以用户友好的方式输出这些值。我不需要再从一个字符串到另一个值。


我使用扩展方法执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public enum ErrorLevel
{
  None,
  Low,
  High,
  SoylentGreen
}

public static class ErrorLevelExtensions
{
  public static string ToFriendlyString(this ErrorLevel me)
  {
    switch(me)
    {
      case ErrorLevel.None:
        return"Everything is OK";
      case ErrorLevel.Low:
        return"SNAFU, if you know what I mean.";
      case ErrorLevel.High:
        return"Reaching TARFU levels";
      case ErrorLevel.SoylentGreen:
        return"ITS PEOPLE!!!!";
      default:
        return"Get your damn dirty hands off me you FILTHY APE!";
    }
  }
}


我使用system.componentModel名称空间中的Description属性。只需修饰枚举:

1
2
3
4
5
6
7
private enum PublishStatusValue
{
    [Description("Not Completed")]
    NotCompleted,
    Completed,
    Error
};

然后使用此代码检索它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static string GetDescription<T>(this T 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();
}


也许我遗漏了一些东西,但是enum.getname出了什么问题?

1
2
3
4
public string GetName(PublishStatusses value)
{
    return Enum.GetName(typeof(PublishStatusses), value)
}

编辑:对于用户友好的字符串,需要通过.resource来完成国际化/本地化,并且可以说,使用基于枚举键的固定键比使用相同的decorator属性要好。


我创建了一个反向扩展方法,将描述转换回枚举值:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static T ToEnumValue<T>(this string enumerationDescription) where T : struct
{
    var type = typeof(T);

    if (!type.IsEnum)
        throw new ArgumentException("ToEnumValue<T>(): Must be of enum type","T");

    foreach (object val in System.Enum.GetValues(type))
        if (val.GetDescription<T>() == enumerationDescription)
            return (T)val;

    throw new ArgumentException("ToEnumValue<T>(): Invalid description for enum" + type.Name,"enumerationDescription");
}


这里最简单的解决方案是使用自定义扩展方法(至少在.NET 3.5中-您可以将其转换为早期框架版本的静态助手方法)。

1
2
3
4
5
6
7
8
public static string ToCustomString(this PublishStatusses value)
{
    switch(value)
    {
        // Return string depending on value.
    }
    return null;
}

我假设您要返回的不是枚举值的实际名称(只需调用ToString就可以得到)。


另一个帖子是Java。不能将方法放在c_中的枚举中。

就这样做:

1
2
PublishStatusses status = ...
String s = status.ToString();

如果要为枚举值使用不同的显示值,可以使用属性和反射。


其他一些避免类/引用类型的更原始的选项:

  • 阵列法
  • 嵌套结构方法

阵列法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private struct PublishStatusses
{
    public static string[] Desc = {
       "Not Completed",
       "Completed",
       "Error"
    };

    public enum Id
    {
        NotCompleted = 0,
        Completed,
        Error
    };
}

用法

1
string desc = PublishStatusses.Desc[(int)PublishStatusses.Id.Completed];

嵌套结构方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private struct PublishStatusses
{
    public struct NotCompleted
    {
        public const int Id = 0;
        public const string Desc ="Not Completed";
    }

    public struct Completed
    {
        public const int Id = 1;
        public const string Desc ="Completed";
    }

    public struct Error
    {
        public const int Id = 2;
        public const string Desc ="Error";
    }            
}

用法

1
2
int id = PublishStatusses.NotCompleted.Id;
string desc = PublishStatusses.NotCompleted.Desc;

更新(2018年9月3日)

扩展方法和上面的第一种技术的混合。

我更喜欢将枚举定义为它们"所属"的位置(最接近它们的源,而不是在一些通用的全局命名空间中)。

1
2
3
4
5
6
7
8
9
10
11
12
namespace ViewModels
{
    public class RecordVM
    {
        //public enum Enum { Minutes, Hours }
        public struct Enum
        {
            public enum Id { Minutes, Hours }
            public static string[] Name = {"Minute(s)","Hour(s)" };
        }
    }
}

扩展方法似乎适用于公共区域,而枚举的"本地化"定义现在使扩展方法更加冗长。

1
2
3
4
5
6
7
8
9
10
namespace Common
{
    public static class EnumExtensions
    {
        public static string Name(this RecordVM.Enum.Id id)
        {
            return RecordVM.Enum.Name[(int)id];
        }
    }  
}

枚举及其扩展方法的用法示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace Views
{
    public class RecordView
    {
        private RecordDataFieldList<string, string> _fieldUnit;

        public RecordView()
        {
            _fieldUnit.List = new IdValueList<string, string>
            {            
                new ListItem<string>((int)RecordVM.Enum.Id.Minutes, RecordVM.Enum.Id.Minutes.Name()),
                new ListItem<string>((int)RecordVM.Enum.Id.Hours, RecordVM.Enum.Id.Hours.Name())
            };
        }

        private void Update()
        {    
            RecordVM.Enum.Id eId = DetermineUnit();

            _fieldUnit.Input.Text = _fieldUnit.List.SetSelected((int)eId).Value;
        }
    }
}

注意:我实际上决定取消Enum包装器(和Name数组),因为最好是名称字符串来自资源(即config file或db),而不是硬编码,并且因为我最终将扩展方法放在ViewModels名称空间(只是放在另一个"commonvm.cs"文件中)。另外,整个.Id的事情变得分心和麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
namespace ViewModels
{
    public class RecordVM
    {
        public enum Enum { Minutes, Hours }
        //public struct Enum
        //{
        //    public enum Id { Minutes, Hours }
        //    public static string[] Name = {"Minute(s)","Hour(s)" };
        //}
    }
}

公用计算机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//namespace Common
namespace ViewModels
{
    public static class EnumExtensions
    {
        public static string Name(this RecordVM.Enum id)
        {
            //return RecordVM.Enum.Name[(int)id];
            switch (id)
            {
                case RecordVM.Enum.Minutes: return"Minute(s)";                    
                case RecordVM.Enum.Hours: return"Hour(s)";
                default: return null;
            }
        }
    }  
}

枚举及其扩展方法的用法示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace Views
{
    public class RecordView
    {
        private RecordDataFieldList<string, string> _fieldUnit

        public RecordView()
        {
            _fieldUnit.List = new IdValueList<string, string>
            {            
                new ListItem<string>((int)RecordVM.Enum.Id.Minutes, RecordVM.Enum.Id.Minutes.Name()),
                new ListItem<string>((int)RecordVM.Enum.Id.Hours, RecordVM.Enum.Id.Hours.Name())
            };
        }

        private void Update()
        {    
            RecordVM.Enum eId = DetermineUnit();

            _fieldUnit.Input.Text = _fieldUnit.List.SetSelected((int)eId).Value;
        }
    }
}


最简单的方法就是将这个扩展类包含到项目中,它将与项目中的任何枚举一起工作:

1
2
3
4
5
6
7
public static class EnumExtensions
{
    public static string ToFriendlyString(this Enum code)
    {
        return Enum.GetName(code.GetType(), code);
    }
}

用途:

1
2
3
4
5
6
enum ExampleEnum
{
    Demo = 0,
    Test = 1,
    Live = 2
}

1
2
ExampleEnum ee = ExampleEnum.Live;
Console.WriteLine(ee.ToFriendlyString());


可以使用Humanizer包和Humanize枚举可能性。斧头:

1
2
3
4
5
6
7
enum PublishStatusses
{
    [Description("Custom description")]
    NotCompleted,
    AlmostCompleted,
    Error
};

然后可以直接对枚举使用Humanize扩展方法:

1
2
3
4
5
var st1 = PublishStatusses.NotCompleted;
var str1 = st1.Humanize(); // will result in Custom description

var st2 = PublishStatusses.AlmostCompleted;
var str2 = st2.Humanize(); // will result in Almost completed (calculated automaticaly)


关于RayBooysen,代码中有一个bug:Enum-ToString和用户友好的字符串

您需要考虑枚举值上的多个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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();


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public enum MyEnum
{
    [Description("Option One")]
    Option_One
}

public static string ToDescriptionString(this Enum This)
{
    Type type = This.GetType();

    string name = Enum.GetName(type, This);

    MemberInfo member = type.GetMembers()
        .Where(w => w.Name == name)
        .FirstOrDefault();

    DescriptionAttribute attribute = member != null
        ? member.GetCustomAttributes(true)
            .Where(w => w.GetType() == typeof(DescriptionAttribute))
            .FirstOrDefault() as DescriptionAttribute
        : null;

    return attribute != null ? attribute.Description : name;
}


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

代替

1
2
3
4
5
private enum PublishStatuses{
    NotCompleted,
    Completed,
    Error
};

具有

1
2
3
4
5
private static class PublishStatuses{
    public static readonly string NotCompleted ="Not Completed";
    public static readonly string Completed ="Completed";
    public static readonly string Error ="Error";
};

像这样用

1
PublishStatuses.NotCompleted; //"Not Completed"

使用顶级"扩展方法"解决方案的问题:

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


以上建议的总结与示例一目了然:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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

用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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


我碰巧是一个vb.net迷,所以这里是我的版本,将DescriptionAttribute方法与扩展方法结合在一起。首先,结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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。"magic"发生在sub main()中的console.writeline调用中,其中最后一个参数只是v.Description。这将返回v1的"1",v2的"2",v3的"3"。此描述方法实际上是一个扩展方法,在另一个名为EnumExtensions的模块中定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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对扩展之类的新主题很生疏)


这是对RayBooysen代码的一个更新,该代码使用通用的getCustomAttributes方法和linq使事情变得更加整洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    /// <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 http://stackoverflow.com/questions/479410/enum-tostring/479417#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();
    }


更清晰的总结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
    }
}

与下划线描述的用法相同。


对于标记枚举,包括。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    public static string Description(this Enum value)
    {
        Type type = value.GetType();

        List<string> res = new List<string>();
        var arrValue = value.ToString().Split(',').Select(v=>v.Trim());
        foreach (string strValue in arrValue)
        {
            MemberInfo[] memberInfo = type.GetMember(strValue);
            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)
                {
                    res.Add(((DescriptionAttribute)attrs.Where(t => t.GetType() == typeof(DescriptionAttribute)).FirstOrDefault()).Description);
                }
                else
                    res.Add(strValue);
            }
            else
                res.Add(strValue);
        }

        return res.Aggregate((s,v)=>s+","+v);
    }

我认为解决问题的最好(也是最简单)方法是为枚举编写一个扩展方法:

1
2
3
4
public static string GetUserFriendlyString(this PublishStatusses status)
    {

    }


如果您想要完全可定制的东西,请在这里尝试我的解决方案:

http://www.kevinwilliampang.com/post/mapping-enums-to-strings-and-strings-to-enums-in-net.aspx

基本上,本文概述了如何将描述属性附加到每个枚举,并提供了一种从枚举映射到描述的通用方法。