使用用户友好的字符串枚举ToString

我的枚举由以下值组成:

private enum PublishStatusses{
NotCompleted,
Completed,
Error
};
我希望能够以一种用户友好的方式输出这些值 我不需要再从字符串到值。< / p >
382826 次浏览

我使用了系统中的Description属性。ComponentModel名称空间。简单地装饰enum:

private enum PublishStatusValue
{
[Description("Not Completed")]
NotCompleted,
Completed,
Error
};

然后使用下面的代码来检索它:

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

如果你想要完全可定制的东西,试试我的解决方案:

http://www.kevinwilliampang.com/post/Mapping-Enums-To-Strings-and-Strings-to-Enums-in-NET.aspx

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

另一篇文章是Java。在c#中,你不能把方法放在枚举中。

就像这样做:

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

如果您想为枚举值使用不同的显示值,您可以使用Attributes和Reflection。

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

public static string ToCustomString(this PublishStatusses value)
{
switch(value)
{
// Return string depending on value.
}
return null;
}

我在这里假设您希望返回enum值的实际名称以外的内容(您可以通过简单地调用ToString来获得)。

也许我遗漏了一些东西,但是枚举。getname有什么问题?

public string GetName(PublishStatusses value)
{
return Enum.GetName(typeof(PublishStatusses), value)
}

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

我用扩展方法做到这一点:

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

关于Ray Booysen,代码中有一个bug: 使用用户友好的字符串枚举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();

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

我碰巧是个VB。这是我的版本,结合了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()中调用WriteLine,其中最后一个参数只是v.Description。它为V1返回“1”,为V2返回“V2”,为V3返回“3”。这个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中,该Dictionary是按需填充的。

(对不起,VB。NET解决方案-将其转换为c#应该相对简单,而我的c#在扩展等新主题上已经生疏了)

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

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

以上建议的干净总结与样本:

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

更简洁的总结是:

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

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

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

public static class EnumExtensions
{
public static string ToFriendlyString(this Enum code)
{
return Enum.GetName(code.GetType(), code);
}
}

用法:

enum ExampleEnum
{
Demo = 0,
Test = 1,
Live = 2
}

...

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

你可以在人性化枚举中使用人性化包。一个eaxample:

enum PublishStatusses
{
[Description("Custom description")]
NotCompleted,
AlmostCompleted,
Error
};

那么你可以直接在enum上使用Humanize扩展方法:

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)

用于标记枚举,包括。

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

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

public static string GetUserFriendlyString(this PublishStatusses status)
{


}

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

  • 数组的方法
  • 嵌套结构方法

数组的方法

private struct PublishStatusses
{
public static string[] Desc = {
"Not Completed",
"Completed",
"Error"
};


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

使用

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

嵌套结构方法

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

使用

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

更新(03/09/2018)

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

我更喜欢将枚举定义在它们“所属”的位置(最接近它们的起源源,而不是某个通用的全局名称空间)。

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

扩展方法似乎适合于公共区域,枚举的“本地化”定义现在使扩展方法更加详细。

namespace Common
{
public static class EnumExtensions
{
public static string Name(this RecordVM.Enum.Id id)
{
return RecordVM.Enum.Name[(int)id];
}
}
}

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

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数组),因为名称字符串最好来自资源(即配置文件或DB)而不是硬编码,并且因为我最终将扩展方法放在ViewModels命名空间中(只是在不同的“CommonVM.cs”文件中)。加上整个.Id的事情变得分心和麻烦。

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)" };
//}
}
}

CommonVM.cs

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

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

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

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

取代

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"

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

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

使用枚举。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

如果您只是想在单词之间添加空格,那么简单到

string res = Regex.Replace(PublishStatusses.NotCompleted, "[A-Z]", " $0").Trim();

我使用一个泛型类来存储枚举/描述对,并使用一个嵌套的helper类来获取描述。

枚举:

enum Status { Success, Fail, Pending }

泛型类:

注意:由于泛型类不能由枚举约束,所以我改为由结构体约束,并在构造函数中检查枚举

public class EnumX<T> where T : struct
{
public T Code { get; set; }
public string Description { get; set; }


public EnumX(T code, string desc)
{
if (!typeof(T).IsEnum) throw new NotImplementedException();


Code = code;
Description = desc;
}


public class Helper
{
private List<EnumX<T>> codes;


public Helper(List<EnumX<T>> codes)
{
this.codes = codes;
}


public string GetDescription(T code)
{
EnumX<T> e = codes.Where(c => c.Code.Equals(code)).FirstOrDefault();
return e is null ? "Undefined" : e.Description;
}
}
}

用法:

EnumX<Status>.Helper StatusCodes = new EnumX<Status>.Helper(new List<EnumX<Status>>()
{
new EnumX<Status>(Status.Success,"Operation was successful"),
new EnumX<Status>(Status.Fail,"Operation failed"),
new EnumX<Status>(Status.Pending,"Operation not complete. Please wait...")
});


Console.WriteLine(StatusCodes.GetDescription(Status.Pending));

根据本文档:https://learn.microsoft.com/pt-br/dotnet/api/system.enum.tostring?view=netframework-4.8

可以使用如下格式将枚举数转换为字符串:

public enum Example
{
Example1,
Example2
}


Console.WriteLine(Example.Example1.ToString("g"));


//Outputs: "Example1"

你可以在这个链接中看到所有可能的格式:https://learn.microsoft.com/pt-br/dotnet/api/system.string?view=netframework-4.8

我已经迟到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")

只需要使用一个静态类来模拟枚举:

public static class PublishStatusses{
public const string NotCompleted = "Not Completed";
public const string Completed = "Completed";
public const string Error = "Error"
};

要访问这些值,只需要像枚举一样使用:

PublishStatusses.NotCompleted;