我想知道是否有可能获得枚举值的属性,而不是枚举本身?例如,假设我有以下枚举:
using System.ComponentModel; // for DescriptionAttribute
enum FunkyAttributesEnum
{
[Description("Name With Spaces1")]
NameWithoutSpaces1,
[Description("Name With Spaces2")]
NameWithoutSpaces2
}
我想要的是给定枚举类型,产生枚举字符串值及其描述的二元组。
价值很简单:
Array values = System.Enum.GetValues(typeof(FunkyAttributesEnum));
foreach (int value in values)
Tuple.Value = Enum.GetName(typeof(FunkyAttributesEnum), value);
但我如何获得描述属性的值,以填充Tuple.Desc?如果属性属于枚举本身,我可以想到如何做到这一点,但我不知道如何从枚举的值中获得它。
public enum DataFilters
{
[Display(Name= "Equals")]
Equals = 1,// Display Name and Enum Name are same
[Display(Name= "Does Not Equal")]
DoesNotEqual = 2, // Display Name and Enum Name are different
}
在这个例子中它会产生错误1 "等于"
public static string GetDisplayName(this Enum enumValue)
{
var enumMember = enumValue.GetType().GetMember(enumValue.ToString()).First();
return enumMember.GetCustomAttribute<DisplayAttribute>() != null ? enumMember.GetCustomAttribute<DisplayAttribute>().Name : enumMember.Name;
}
如果它是相同的返回enum名称而不是显示名称因为
enumMember.GetCustomAttribute ()
如果displayname和enum name相同,则返回null .....
性能问题
如果你想要更好的性能,这是一种方法:
public static class AdvancedEnumExtensions
{
/// <summary>
/// Gets the custom attribute <typeparamref name="T"/> for the enum constant, if such a constant is defined and has such an attribute; otherwise null.
/// </summary>
public static T GetCustomAttribute<T>(this Enum value) where T : Attribute
{
return GetField(value)?.GetCustomAttribute<T>(inherit: false);
}
/// <summary>
/// Gets the FieldInfo for the enum constant, if such a constant is defined; otherwise null.
/// </summary>
public static FieldInfo GetField(this Enum value)
{
ulong u64 = ToUInt64(value);
return value
.GetType()
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
.Where(f => ToUInt64(f.GetRawConstantValue()) == u64)
.FirstOrDefault();
}
/// <summary>
/// Checks if an enum constant is defined for this enum value
/// </summary>
public static bool IsDefined(this Enum value)
{
return GetField(value) != null;
}
/// <summary>
/// Converts the enum value to UInt64
/// </summary>
public static ulong ToUInt64(this Enum value) => ToUInt64((object)value);
private static ulong ToUInt64(object value)
{
switch (Convert.GetTypeCode(value))
{
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
return unchecked((ulong)Convert.ToInt64(value, CultureInfo.InvariantCulture));
case TypeCode.Byte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Char:
case TypeCode.Boolean:
return Convert.ToUInt64(value, CultureInfo.InvariantCulture);
default: throw new InvalidOperationException("UnknownEnumType");
}
}
}
为什么这个有更好的性能?
因为内置方法都使用与此非常相似的代码,除了它们还运行一堆我们不关心的其他代码。c#的Enum代码通常都很糟糕。
上面的代码已经经过linq化和精简,所以它只包含我们关心的部分。
为什么内置代码很慢?
首先关于枚举. tostring () -vs-枚举. getname (..)
总是使用后者。(或者两者都不做更好,如下文将会清楚说明。)
ToString()在内部使用后者,但同样,也做了一堆我们不想要的其他事情,例如尝试组合标志,打印出数字等。我们只对枚举中定义的常量感兴趣。
枚举。GetName依次获取所有字段,为所有名称创建一个字符串数组,在它们的所有RawConstantValues上使用上述ToUInt64创建一个包含所有值的UInt64数组,根据UInt64值对两个数组进行排序,最后通过在UInt64-array中执行BinarySearch来找到我们想要的值的索引,从而从name-array中获取名称。
...然后我们把字段和排序的数组扔掉用这个名字重新找到字段。
一个词:“呃!”
下面是从Display属性获取信息的代码。它使用泛型方法检索属性。如果没有找到该属性,它将枚举值转换为字符串,将pascal/camel大小写转换为标题大小写(此处获得的代码)
public static class EnumHelper
{
// Get the Name value of the Display attribute if the
// enum has one, otherwise use the value converted to title case.
public static string GetDisplayName<TEnum>(this TEnum value)
where TEnum : struct, IConvertible
{
var attr = value.GetAttributeOfType<TEnum, DisplayAttribute>();
return attr == null ? value.ToString().ToSpacedTitleCase() : attr.Name;
}
// Get the ShortName value of the Display attribute if the
// enum has one, otherwise use the value converted to title case.
public static string GetDisplayShortName<TEnum>(this TEnum value)
where TEnum : struct, IConvertible
{
var attr = value.GetAttributeOfType<TEnum, DisplayAttribute>();
return attr == null ? value.ToString().ToSpacedTitleCase() : attr.ShortName;
}
/// <summary>
/// Gets an attribute on an enum field value
/// </summary>
/// <typeparam name="TEnum">The enum type</typeparam>
/// <typeparam name="T">The type of the attribute you want to retrieve</typeparam>
/// <param name="value">The enum value</param>
/// <returns>The attribute of type T that exists on the enum value</returns>
private static T GetAttributeOfType<TEnum, T>(this TEnum value)
where TEnum : struct, IConvertible
where T : Attribute
{
return value.GetType()
.GetMember(value.ToString())
.First()
.GetCustomAttributes(false)
.OfType<T>()
.LastOrDefault();
}
}
这是字符串转换为title case的扩展方法:
/// <summary>
/// Converts camel case or pascal case to separate words with title case
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static string ToSpacedTitleCase(this string s)
{
//https://stackoverflow.com/a/155486/150342
CultureInfo cultureInfo = Thread.CurrentThread.CurrentCulture;
TextInfo textInfo = cultureInfo.TextInfo;
return textInfo
.ToTitleCase(Regex.Replace(s,
"([a-z](?=[A-Z0-9])|[A-Z](?=[A-Z][a-z]))", "$1 "));
}
流利的一句话…
这里我使用的DisplayAttribute包含Name和Description属性。
public static DisplayAttribute GetDisplayAttributesFrom(this Enum enumValue, Type enumType)
{
return enumType.GetMember(enumValue.ToString())
.First()
.GetCustomAttribute<DisplayAttribute>();
}
例子
public enum ModesOfTransport
{
[Display(Name = "Driving", Description = "Driving a car")] Land,
[Display(Name = "Flying", Description = "Flying on a plane")] Air,
[Display(Name = "Sea cruise", Description = "Cruising on a dinghy")] Sea
}
void Main()
{
ModesOfTransport TransportMode = ModesOfTransport.Sea;
DisplayAttribute metadata = TransportMode.GetDisplayAttributesFrom(typeof(ModesOfTransport));
Console.WriteLine("Name: {0} \nDescription: {1}", metadata.Name, metadata.Description);
}
输出
Name: Sea cruise
Description: Cruising on a dinghy