获取枚举值的属性

573

我想知道是否可以获取enum值的属性而不是enum本身的属性?例如,假设我有以下enum

using System.ComponentModel; // for DescriptionAttribute

enum FunkyAttributesEnum
{
    [Description("Name With Spaces1")]
    NameWithoutSpaces1,    
    [Description("Name With Spaces2")]
    NameWithoutSpaces2
}
我希望的是,对于给定的枚举类型,能够生成枚举字符串值和其描述的2元组。 处理枚举值很容易:
Array values = System.Enum.GetValues(typeof(FunkyAttributesEnum));
foreach (int value in values)
    Tuple.Value = Enum.GetName(typeof(FunkyAttributesEnum), value);

但是我该如何获取描述属性的值,以填充Tuple.Desc?如果属性属于enum本身,我能想到如何做到这一点,但是我不知道如何从enum的值中获取它。


从另一个问题https://dev59.com/NnRB5IYBdhLWcg3w6bYE#4778347 - finnw
3
Description所需的命名空间是System.ComponentModel。 - John M
你也可以不使用System.ComponentModel,而是使用自己的属性类型;DescriptionAttribute并没有什么特别之处。 - jrh
请查看此链接:https://dev59.com/8Lfna4cB1Zd3GeqPv5sC#58954215 - Amin Golmahalle
27个回答

544

这应该可以满足你的需求。

try
{
  var enumType = typeof(FunkyAttributesEnum);
  var memberInfos = 
  enumType.GetMember(FunkyAttributesEnum.NameWithoutSpaces1.ToString());
  var enumValueMemberInfo = memberInfos.FirstOrDefault(m => 
  m.DeclaringType == enumType);
  var valueAttributes = 
  enumValueMemberInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
  var description = ((DescriptionAttribute)valueAttributes[0]).Description;
}
catch
{
    return FunkyAttributesEnum.NameWithoutSpaces1.ToString()
}


12
可选择使用 type.GetFields(BindingFlags.Public | BindingFlags.Static) 一次性获取所有的 memInfos。 - TrueWill
4
除了我需要写 typeof(FunkyAttributesEnum) 以外,其他都很好用。谢谢。 - Greg Randall
@AlexK,我没有看到Enum类有NameWithoutSpaces1属性。FunkyAttributesEnum.NameWithoutSpaces1是从哪里来的? - Don
3
@Don,这是来自原帖问题的枚举成员名称。 - MEMark

375
这段代码应该为您提供一个很好的扩展方法,让您可以在任何枚举上检索通用属性。我认为它与上面的lambda函数不同,因为它更简单易用,并且只需要传递通用类型即可。
public static class EnumHelper
{
    /// <summary>
    /// Gets an attribute on an enum field value
    /// </summary>
    /// <typeparam name="T">The type of the attribute you want to retrieve</typeparam>
    /// <param name="enumVal">The enum value</param>
    /// <returns>The attribute of type T that exists on the enum value</returns>
    /// <example><![CDATA[string desc = myEnumVariable.GetAttributeOfType<DescriptionAttribute>().Description;]]></example>
    public static T GetAttributeOfType<T>(this Enum enumVal) where T:System.Attribute
    {
        var type = enumVal.GetType();
        var memInfo = type.GetMember(enumVal.ToString());
        var attributes = memInfo[0].GetCustomAttributes(typeof(T), false);
        return (attributes.Length > 0) ? (T)attributes[0] : null;
    }
}

使用方法如下:

string desc = myEnumVariable.GetAttributeOfType<DescriptionAttribute>().Description;

2
我喜欢这个比Scott的更多,因为使用起来更干净(少打字),所以+1 :) - nawfal
3
若没有属性存在,这会引发“IndexOutOfRangeException”的异常吗? - Erik Philips
7
最好使用type.GetMember(Enum.GetName(type, enumVal))代替memInfo,因为对于不同的区域设置,enumVal.ToString()可能不可靠。 - Lin Song Yang
5
为什么要调用 GetCustomAttributes() 然后获取第一个元素,而不是直接调用 GetCustomAttribute() - tigrou
6
@tigrou,这个扩展是最近添加到.NET框架中的;解决方案(来自2009年)可能需要更新。 - Manuel Faux
显示剩余2条评论

94

这是一个使用lambda表达式进行选择的通用实现

public static Expected GetAttributeValue<T, Expected>(this Enum enumeration, Func<T, Expected> expression)
    where T : Attribute
{
    T attribute =
      enumeration
        .GetType()
        .GetMember(enumeration.ToString())
        .Where(member => member.MemberType == MemberTypes.Field)
        .FirstOrDefault()
        .GetCustomAttributes(typeof(T), false)
        .Cast<T>()
        .SingleOrDefault();

    if (attribute == null)
        return default(Expected);

    return expression(attribute);
}

这样调用它:

string description = targetLevel.GetAttributeValue<DescriptionAttribute, string>(x => x.Description);

5
太好了。我们只需小心处理给定的枚举值是否为组合值(由“FlagsAttribute”允许)。在这种情况下,“enumeration.GetType().GetMember(enumeration.ToString())[0]”将失败。 - remio
你可以写得更短:value.GetType().GetField(value.ToString()).GetCustomAttributes(false).OfType<T>().SingleOrDefault(),但必须承认你的显式方式更好。 - nawfal
2
我还添加了以下代码:public static String GetDescription(this Enum enumeration) { return enumeration.GetAttributeValue(x => x.Description); }这样,只需要使用 targetLevel.GetDescription(); 即可。 - MarkKGreenway

85

我将几个答案合并在一起,创建了一个更具可扩展性的解决方案。我提供它只是为了以后有用的人。原始帖子在这里

using System;
using System.ComponentModel;

public static class EnumExtensions {

    // This extension method is broken out so you can use a similar pattern with 
    // other MetaData elements in the future. This is your base method for each.
    public static T GetAttribute<T>(this Enum value) where T : Attribute {
        var type = value.GetType();
        var memberInfo = type.GetMember(value.ToString());
        var attributes = memberInfo[0].GetCustomAttributes(typeof(T), false);
        return attributes.Length > 0 
          ? (T)attributes[0]
          : null;
    }

    // This method creates a specific call to the above method, requesting the
    // Description MetaData attribute.
    public static string ToName(this Enum value) {
        var attribute = value.GetAttribute<DescriptionAttribute>();
        return attribute == null ? value.ToString() : attribute.Description;
    }

}

这个解决方案在Enum上创建了一对扩展方法。第一个方法允许您使用反射来检索与您的值相关联的任何属性。第二个方法专门调用检索DescriptionAttribute并返回其Description值。

例如,考虑使用来自System.ComponentModelDescriptionAttribute属性。

using System.ComponentModel;

public enum Days {
    [Description("Sunday")]
    Sun,
    [Description("Monday")]
    Mon,
    [Description("Tuesday")]
    Tue,
    [Description("Wednesday")]
    Wed,
    [Description("Thursday")]
    Thu,
    [Description("Friday")]
    Fri,
    [Description("Saturday")]
    Sat
}

要使用上面的扩展方法,现在只需调用以下内容:
Console.WriteLine(Days.Mon.ToName());

或者

var day = Days.Mon;
Console.WriteLine(day.ToName());

在最后一行,你的意思是 "attribute.Description" 吗? 返回 attribute == null ? value.ToString() : attribute.Description; - Jeson Martajaya
3
最好和最简单的答案。 - Alek Davis

38

除了AdamCrawford的回答之外,我还进一步创建了更专业的扩展方法,以便从中提取描述。

public static string GetAttributeDescription(this Enum enumValue)
{
    var attribute = enumValue.GetAttributeOfType<DescriptionAttribute>();
    return attribute == null ? String.Empty : attribute.Description;
} 

因此,要获取说明,您可以使用原始的扩展方法:

string desc = myEnumVariable.GetAttributeOfType<DescriptionAttribute>().Description

或者您可以直接在此处调用扩展方法,如下所示:

string desc = myEnumVariable.GetAttributeDescription();

这应该会让你的代码更易读。


24

流畅的一行代码...

在这里我使用了包含NameDescription属性的DisplayAttribute

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

3
我也使用这个,它是所有答案中最清洁的!+1 - Mafii
这似乎非常有用!谢谢 - Irf
你可以使用 enumValue.GetType() 来消除 enumType 参数。 - tponthieux

7

以下是获取Display属性信息的代码。它使用通用方法来检索属性。如果未找到该属性,则将枚举值转换为帕斯卡/骆驼大小写转换为标题大小写的字符串(代码来源这里)。

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

这是用于将字符串转换为标题格式的扩展方法:

    /// <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://dev59.com/5nVC5IYBdhLWcg3w4VX6#155486
        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 "));
    }

5

为了一些程序员的幽默感,以下是一句笑话的单行代码:

public static string GetDescription(this Enum value) => value.GetType().GetMember(value.ToString()).First().GetCustomAttribute<DescriptionAttribute>() is DescriptionAttribute attribute ? attribute.Description : string.Empty;

以更易读的形式:

using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

public static class EnumExtensions
{
    // get description from enum:

    public static string GetDescription(this Enum value)
    {
        return value.GetType().
            GetMember(value.ToString()).
            First().
            GetCustomAttribute<DescriptionAttribute>() is DescriptionAttribute attribute
            ? attribute.Description
            : throw new Exception($"Enum member '{value.GetType()}.{value}' doesn't have a [DescriptionAttribute]!");
    }

    // get enum from description:

    public static T GetEnum<T>(this string description) where T : Enum
    {
        foreach (FieldInfo fieldInfo in typeof(T).GetFields())
        {
            if (fieldInfo.GetCustomAttribute<DescriptionAttribute>() is DescriptionAttribute attribute && attribute.Description == description)
                return (T)fieldInfo.GetRawConstantValue();
        }

        throw new Exception($"Enum '{typeof(T)}' doesn't have a member with a [DescriptionAttribute('{description}')]!");
    }
}

有趣的是,我实际上更喜欢第一种实现方式。我更习惯在C#中使用函数式编程风格,觉得它更易读。 :) - undefined

4
我在这里提供一个关于从枚举属性设置下拉框的答案,这很棒。
然后我需要编写反向代码,以便可以从框中获取选择并返回正确类型的枚举值。
我还修改了代码,以处理属性缺失的情况。
为了方便下一个人,这是我的最终解决方案。
public static class Program
{
   static void Main(string[] args)
    {
       // display the description attribute from the enum
       foreach (Colour type in (Colour[])Enum.GetValues(typeof(Colour)))
       {
            Console.WriteLine(EnumExtensions.ToName(type));
       }

       // Get the array from the description
       string xStr = "Yellow";
       Colour thisColour = EnumExtensions.FromName<Colour>(xStr);

       Console.ReadLine();
    }

   public enum Colour
   {
       [Description("Colour Red")]
       Red = 0,

       [Description("Colour Green")]
       Green = 1,

       [Description("Colour Blue")]
       Blue = 2,

       Yellow = 3
   }
}

public static class EnumExtensions
{

    // This extension method is broken out so you can use a similar pattern with 
    // other MetaData elements in the future. This is your base method for each.
    public static T GetAttribute<T>(this Enum value) where T : Attribute
    {
        var type = value.GetType();
        var memberInfo = type.GetMember(value.ToString());
        var attributes = memberInfo[0].GetCustomAttributes(typeof(T), false);

        // check if no attributes have been specified.
        if (((Array)attributes).Length > 0)
        {
            return (T)attributes[0];
        }
        else
        {
            return null;
        }
    }

    // This method creates a specific call to the above method, requesting the
    // Description MetaData attribute.
    public static string ToName(this Enum value)
    {
        var attribute = value.GetAttribute<DescriptionAttribute>();
        return attribute == null ? value.ToString() : attribute.Description;
    }

    /// <summary>
    /// Find the enum from the description attribute.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="desc"></param>
    /// <returns></returns>
    public static T FromName<T>(this string desc) where T : struct
    {
        string attr;
        Boolean found = false;
        T result = (T)Enum.GetValues(typeof(T)).GetValue(0);

        foreach (object enumVal in Enum.GetValues(typeof(T)))
        {
            attr = ((Enum)enumVal).ToName();

            if (attr == desc)
            {
                result = (T)enumVal;
                found = true;
                break;
            }
        }

        if (!found)
        {
            throw new Exception();
        }

        return result;
    }
}

}


1
哇,我看到了很多愚蠢和不可解释的解决方案,而你的解决方案真是太棒了。非常感谢你 <3 - Kadaj

4
如果你的enum包含像Equals这样的值,你可能会在使用一些扩展时遇到一些错误。这是因为通常假定typeof(YourEnum).GetMember(YourEnum.Value)只会返回一个值,即你的enumMemberInfo。这里有一个稍微安全一点的版本Adam Crawford's answer
public static class AttributeExtensions
{
    #region Methods

    public static T GetAttribute<T>(this Enum enumValue) where T : Attribute
    {
        var type = enumValue.GetType();
        var memberInfo = type.GetMember(enumValue.ToString());
        var member = memberInfo.FirstOrDefault(m => m.DeclaringType == type);
        var attribute = Attribute.GetCustomAttribute(member, typeof(T), false);
        return attribute is T ? (T)attribute : null;
    }

    #endregion
}

编辑(2023年5月3日)

GetField 在这种情况下运作良好。 typeof(YourEnum).GetField(YourEnum.Value) 应该能够产生所需的结果。

public static T GetAttribute<T>(this Enum value) where T : Attribute
{
    Type type = value.GetType();
    FieldInfo field = type.GetField(value.ToString());

    Attribute attr = Attribute.GetCustomAttribute(field, typeof(T), false);
    return (T)attr;
}

感谢mjwills指出这一点。


可以使用GetField而不是GetMember吗? - mjwills
是的,看起来你确实可以。我会相应地更新答案,很好的发现! - Prince Owen

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