ZeroBased_IX
ZeroBased_IX

Reputation: 2727

Enum Attribute order changing after first access

We have the following enum:

public enum TrafficLight
    {
        [Description("../../images/dot-red.png")]
        [AwesomeIcon("<i class='fa fa-lg fa-circle v4-red'></i>")]
        Red = 0,
        [Description("../../images/dot-yellow.png")]
        [AwesomeIcon("<i class='fa fa-lg fa-circle v4-yellow'></i>")]
        Yellow = 1,
        [Description("../../images/dot-green.png")]
        [AwesomeIcon("<i class='fa fa-lg fa-circle v4-green'></i>")]
        Green = 2,
        [Description("../../images/dot-gold.png")]
        [AwesomeIcon("<i class='fa fa-lg fa-circle v4-gold'></i>")]
        Gold = 3,
        [Description("../../images/dot-grey.png")]
        [AwesomeIcon("<i class='fa fa-lg fa-circle v4-grey'></i>")]
        Grey = 99
    }

AwesomeIcon inherits from the DescriptionAttribute class:

 public class AwesomeIcon : DescriptionAttribute
    {
        public AwesomeIcon(string icon)
            : base(icon)
        {

        }
    }

The problem is when the Enum description is accessed more than once, the order of the attributes changes. For example, we're getting the description like so:

 public static string GetEnumDescription(Enum value, int index = 0)
        {
            var fi = value.GetType().GetField(value.ToString());

            var attributes =
                fi.GetCustomAttributes(typeof (DescriptionAttribute), false) as DescriptionAttribute[];

            if(attributes == null)
            {
                return string.Empty;
            }

            if (attributes.Any() && index <= attributes.Length)
            {
                return attributes[index].Description;
            }   

            return value.ToString();
        }

First Access

GetEnumDescription(TrafficLight.Red);
//Returns "../../images/dot-red.png" as expected.
//Debug GetEnumDescription attributes variable
[0]Description
[1]AwesomeIcon

Second Access

GetEnumDescription(TrafficLight.Yellow);
//Returns "<i class='fa fa-lg fa-circle v4-yellow'></i>" 
// which is the value of AwesomeIcon.
//Debug GetEnumDescription attributes variable
[0]AwesomeIcon
[1]Description

Every access after the order remains AwesomeIcon, then Description.

The problem is that AwesomeIcon inherits from Description and it's getting picked up. If the order remains the same in the array it wouldn't be a problem, I can reference it by index.

I've not experienced this before, any ideas?

Upvotes: 0

Views: 78

Answers (2)

vendettamit
vendettamit

Reputation: 14677

As a quick fix you can sort the attributes by Names, this will help you getting them in the same order every time.

var attributes =
         fi.GetCustomAttributes(typeof (DescriptionAttribute), false) as object[];

if(attributes.Any())
  {
   // Ascending order
   Array.Sort(attributes, (x, y) => 
    String.Compare(x.GetType().Name, y.GetType().Name)); 
  }

And fix a bug in GetEnumDescription(Enum value, int index = 0):

if (attributes.Any() && index <= attributes.Length) 

should be

if (attributes.Any() && index < attributes.Length)

Upvotes: 1

Jcl
Jcl

Reputation: 28272

You can make sure you have a DescriptionAttribute (even if it's inherited), with something like:

var fi = value.GetType().GetField(value.ToString());
var attributes = fi.GetCustomAttributes(typeof (DescriptionAttribute), false);
var myDescriptionAttribute = 
      attributes.FirstOrDefault(x => x.GetType() == typeof(DescriptionAttribute)) as DescriptionAttribute;

And forget about indices. Attributes are not guaranteed an order so an index is rather useless.

By using x => x.GetType() == typeof(DescriptionAttribute) you get an exact type match.

I've made a fiddle demonstrating two possible methods (using generics or just finding the DescriptionAttribute): https://dotnetfiddle.net/StUvqf

But basically:

public static string GetEnumDescription(Enum value)
{
    var fi = value.GetType().GetField(value.ToString());
    var attributes = fi.GetCustomAttributes(typeof (DescriptionAttribute), false);
    var theDescriptionAttribute = attributes.FirstOrDefault(x => x.GetType() == typeof (DescriptionAttribute)) as DescriptionAttribute;
    if (theDescriptionAttribute == null)
    {
        return string.Empty;
    }
    return theDescriptionAttribute.Description;
}

or:

public static string GetEnumDescription<T>(Enum value)
    where T : DescriptionAttribute
{
    var fi = value.GetType().GetField(value.ToString());
    var attributes = fi.GetCustomAttributes(typeof (T), false);
    var theDescriptionAttribute = attributes.FirstOrDefault(x => x.GetType() == typeof (T)) as T;
    if (theDescriptionAttribute == null)
    {
        return string.Empty;
    }
    return theDescriptionAttribute.Description;
}

Of course might need some more null checkings, but I'll leave that to you

As an extra hint, FieldInfo.GetCustomAttributes never returns null (it may return an empty array, but not null), so you could just remove that null check :-)

Upvotes: 1

Related Questions