60

I have an enum where each member has a custom attribute applied to it. How can I retrieve the value stored in each attribute?

Right now I do this:

var attributes = typeof ( EffectType ).GetCustomAttributes ( false );
foreach ( object attribute in attributes )
{
    GPUShaderAttribute attr = ( GPUShaderAttribute ) attribute;
    if ( attr != null )
        return attr.GPUShader;
}
return 0;

Another issue is, if it's not found, what should I return? 0 is implcity convertible to any enum, right? That's why I returned that.

Forgot to mention, the above code returns 0 for every enum member.

Joan Venge
  • 315,713
  • 212
  • 479
  • 689
  • 1
    possible duplicate of [What AttributeTarget should I use for enum members?](http://stackoverflow.com/questions/5032774/what-attributetarget-should-i-use-for-enum-members) – Hans Passant Feb 23 '11 at 22:14
  • 2
    No this is different. Here I am just trying to get the custom attributes set on an enum member using reflection. – Joan Venge Feb 23 '11 at 22:16
  • 1
    possible duplicate of [Getting attributes of Enum's value](http://stackoverflow.com/questions/1799370/getting-attributes-of-enums-value) – Roman Starkov Jan 23 '12 at 10:25

7 Answers7

92

Try using a generic method

Attribute:

class DayAttribute : Attribute
{
    public string Name { get; private set; }

    public DayAttribute(string name)
    {
        this.Name = name;
    }
}

Enum:

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

Generic method:

        public static TAttribute GetAttribute<TAttribute>(this Enum value)
        where TAttribute : Attribute
    {
        var enumType = value.GetType();
        var name = Enum.GetName(enumType, value);
        return enumType.GetField(name).GetCustomAttributes(false).OfType<TAttribute>().SingleOrDefault();
    }

Invoke:

        static void Main(string[] args)
    {
        var day = Days.Mon;
        Console.WriteLine(day.GetAttribute<DayAttribute>().Name);
        Console.ReadLine();
    }

Result:

Monday

George Kargakis
  • 4,940
  • 3
  • 16
  • 12
  • 7
    Wish I could upvote this answer 100 times!! Great stuff :-) – Gericke Mar 01 '18 at 20:44
  • 2
    Yeah ok works.. but you may be calling .Name on a null if you do not declare an attrib for a particular Enum value. i.e. You should check for day.GetAttribute() != null before calling .Name – joedotnot Apr 06 '19 at 01:42
46

It is a bit messy to do what you are trying to do as you have to use reflection:

public GPUShaderAttribute GetGPUShader(EffectType effectType)
{
    MemberInfo memberInfo = typeof(EffectType).GetMember(effectType.ToString())
                                              .FirstOrDefault();

    if (memberInfo != null)
    {
        GPUShaderAttribute attribute = (GPUShaderAttribute) 
                     memberInfo.GetCustomAttributes(typeof(GPUShaderAttribute), false)
                               .FirstOrDefault();
        return attribute;
    }

    return null;
}

This will return an instance of the GPUShaderAttribute that is relevant to the one marked up on the enum value of EffectType. You have to call it on a specific value of the EffectType enum:

GPUShaderAttribute attribute = GetGPUShader(EffectType.MyEffect);

Once you have the instance of the attribute, you can get the specific values out of it that are marked-up on the individual enum values.

adrianbanks
  • 81,306
  • 22
  • 176
  • 206
  • Thanks man, it works. I didn't know it would be this complicated. But this is the simplest way, right? Also do you know why my version didn't work. I thought since enums can't be instanced, using enum.getCustomAttributes would work. – Joan Venge Feb 23 '11 at 22:32
  • 1
    @Joan: This is the simplest way as far I know. Your method didn't work as you were getting the attributes defined on the enum type instead of on the values of the type. – adrianbanks Feb 23 '11 at 22:35
  • Thanks Adrian, that makes sense now. – Joan Venge Feb 23 '11 at 22:45
29

There is another method to do this with generics:

public static T GetAttribute<T>(Enum enumValue) where T: Attribute
{
    T attribute;

    MemberInfo memberInfo = enumValue.GetType().GetMember(enumValue.ToString())
                                    .FirstOrDefault();

    if (memberInfo != null)
    {
        attribute = (T) memberInfo.GetCustomAttributes(typeof (T), false).FirstOrDefault();
        return attribute;
    }
    return null;
}
Joan Venge
  • 315,713
  • 212
  • 479
  • 689
user1779271
  • 299
  • 3
  • 2
  • 2
    I like this, but it doesn't take into account the possibility of having multiple instances of the same attribute. I took what you had and modified it to used T[] instead of T, then removed the FirstOrDefault() on the GetCustomAttributes. – Kevin Heidt Jul 25 '15 at 22:54
2

Assuming GPUShaderAttribute:

[AttributeUsage(AttributeTargets.Field,AllowMultiple =false)]
public class GPUShaderAttribute: Attribute
{
    public GPUShaderAttribute(string value)
    {
        Value = value;
    }
    public string Value { get; internal set; }
}

Then we could write a few generic methods to return a dictionary of the enum values and the GPUShaderAttribute object.

    /// <summary>
    /// returns the attribute for a given enum
    /// </summary>        
    public static TAttribute GetAttribute<TAttribute>(IConvertible @enum)
    {
        TAttribute attributeValue = default(TAttribute);
        if (@enum != null)
        {
            FieldInfo fi = @enum.GetType().GetField(@enum.ToString());
            attributeValue = fi == null ? attributeValue : (TAttribute)fi.GetCustomAttributes(typeof(TAttribute), false).DefaultIfEmpty(null).FirstOrDefault();

        }
        return attributeValue;
    }

Then return the whole set with this method.

/// <summary>
/// Returns a dictionary of all the Enum fields with the attribute.
/// </summary>
public static Dictionary<Enum, RAttribute> GetEnumObjReference<TEnum, RAttribute>()
{
    Dictionary<Enum, RAttribute> _dict = new Dictionary<Enum, RAttribute>();
    Type enumType = typeof(TEnum);
    Type enumUnderlyingType = Enum.GetUnderlyingType(enumType);
    Array enumValues = Enum.GetValues(enumType);
    foreach (Enum enumValue in enumValues)
    {
        _dict.Add(enumValue, GetAttribute<RAttribute>(enumValue));
    }

    return _dict;
}

If you just wanted a string value I would recommend a slightly different route.

    /// <summary>
    /// Returns the string value of the custom attribute property requested.
    /// </summary>
    public static string GetAttributeValue<TAttribute>(IConvertible @enum, string propertyName = "Value")
    {
        TAttribute attribute = GetAttribute<TAttribute>(@enum);
        return attribute == null ? null : attribute.GetType().GetProperty(propertyName).GetValue(attribute).ToString();

    }

    /// <summary>
    /// Returns a dictionary of all the Enum fields with the string of the property from the custom attribute nulls default to the enumName
    /// </summary>
    public static Dictionary<Enum, string> GetEnumStringReference<TEnum, RAttribute>(string propertyName = "Value")
    {
        Dictionary<Enum, string> _dict = new Dictionary<Enum, string>();
        Type enumType = typeof(TEnum);
        Type enumUnderlyingType = Enum.GetUnderlyingType(enumType);
        Array enumValues = Enum.GetValues(enumType);
        foreach (Enum enumValue in enumValues)
        {
            string enumName = Enum.GetName(typeof(TEnum), enumValue);
            string decoratorValue = Common.GetAttributeValue<RAttribute>(enumValue, propertyName) ?? enumName;
            _dict.Add(enumValue, decoratorValue);
        }

        return _dict;
    }
MLam
  • 53
  • 1
  • 5
2

I came up with a different method to locate the FieldInfo element for the targeted enumerated value. Locating the enumerated value by converting it to a string felt wrong, so I opted for checking the field list with LINQ:

Type enumType = value.GetType();
FieldInfo[] fields = enumType.GetFields();
FieldInfo fi = fields.Where(tField =>
    tField.IsLiteral &&
    tField.GetValue(null).Equals(value)
    ).First();

So all glommed together I have:

    public static TAttribute GetAttribute<TAttribute>(this Enum value) 
        where TAttribute : Attribute
    {

        Type enumType = value.GetType();
        FieldInfo[] fields = enumType.GetFields();
        FieldInfo fi = fields.Where(tField =>
            tField.IsLiteral &&
            tField.GetValue(null).Equals(value)
            ).First();

        // If we didn't get, return null
        if (fi == null) return null;

        // We found the element (which we always should in an enum)
        // return the attribute if it exists.
        return (TAttribute)(fi.GetCustomAttribute(typeof(TAttribute)));
    }
Ben Keene
  • 459
  • 4
  • 7
1
public string GetEnumAttributeValue(Enum enumValue, Type attributeType, string attributePropertyName)
        {
            /* New generic version (GetEnumDescriptionAttribute results can be achieved using this new GetEnumAttribute with a call like (enumValue, typeof(DescriptionAttribute), "Description")
             * Extracts a given attribute value from an enum:
             *
             * Ex:
             * public enum X
             * {
                     [MyAttribute(myProp = "aaaa")]
             *       x1,
             *       x2,
             *       [Description("desc")]
             *       x3
             * }
             *
             * Usage:
             *      GetEnumAttribute(X.x1, typeof(MyAttribute), "myProp") returns "aaaa"
             *      GetEnumAttribute(X.x2, typeof(MyAttribute), "myProp") returns string.Empty
             *      GetEnumAttribute(X.x3, typeof(DescriptionAttribute), "Description") returns "desc"
             */

            var attributeObj = enumValue.GetType()?.GetMember(enumValue.ToString())?.FirstOrDefault()?.GetCustomAttributes(attributeType, false)?.FirstOrDefault();

            if (attributeObj == null)
                return string.Empty;
            else
            {
                try
                {
                    var attributeCastedObj = Convert.ChangeType(attributeObj, attributeType);
                    var attributePropertyValue = attributeType.GetProperty(attributePropertyName)?.GetValue(attributeCastedObj);
                    return attributePropertyValue?.ToString() ?? string.Empty;
                }
                catch (Exception ex)
                {
                    return string.Empty;
                }
            }
        }
Veverke
  • 9,208
  • 4
  • 51
  • 95
0

Posting as a response to @George Kargakis's answer (not enough rep to comment bc I'm a lurker):

I just used your method, except I named the method GetAttributeOrDefault for clarification.

Also, future reader , you can avoid NullReferenceException and ugly null checking on any properties of the resulting attribute using null propagation, like so: day.GetAttribute<DayAttribute>()?.Name.

Justin
  • 21
  • 6