67

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.

3

7 Answers 7

107

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

Sign up to request clarification or add additional context in comments.

5 Comments

Wish I could upvote this answer 100 times!! Great stuff :-)
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<DayAttribute>() != null before calling .Name
At last efficient code !
This example works really well.... I would give this one a go.
The whole function body can be simplified to => value.GetType().GetField(value.ToString())?.GetCustomAttribute<TAttribute>(false);
47

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.

3 Comments

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: 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.
Thanks Adrian, that makes sense now.
30

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

1 Comment

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

Comments

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

1 Comment

This is a nice clean generic solution. Works great!
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;
                }
            }
        }

Comments

-1

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.