Samuel
Samuel

Reputation: 584

Iterate over Enum elements (Not just names and not just values)

I have an enumeration of enum elements and description attributes

    public enum CourseGrades
    {
        [Description("A")]
        A = 11,
        [Description("A-")]
        AMinus = 10,
        [Description("B+")]
        BPlus = 9,
        [Description("B")]
        B = 8,
        [Description("B-")]
        BMinus = 7,
        [Description("C+")]
        CPlus = 6,
        [Description("C")]
        C = 5,
        [Description("C-")]
        CMinus = 4,
        [Description("D+")]
        DPlus = 3,
        [Description("D")]
        D = 2,
        [Description("D-")]
        DMinus = 1,
        [Description("E")]
        E = 0,
        [Description("Audit")]
        AU = -1,
        [Description("Incomplete")]
        IC = -2,
        [Description("Withdrawl")]
        WD = -3,
        [Description("Not Applicable")]
        NA = -4
    }

My issue is that I am trying to build a list of the descriptions and I am getting stuck on how to do that. All of the answers I have seen say to use Enum.GetNames() or Enum.GetValues() which I know how to do.

The issue with both of those is that they return a string array which has lost all context on where it came from so that I am not able to follow it to get the description for the particular enum value that I want.

The way I get the description right now is to call CourseGrades.A.GetDescription() because I have an extension method that handles getting the description attribute view code.

I've been hoping to do something like

var elements = System.Enum.GetElements(CourseGrades);
var dict = new Dictionary<CourseGrades, string>();

foreach (var element in elements) {
    dict.Add(element, element.GetDescription());
}

but I am beginning to think that something like this isn't possible to do.

I have been trying hard to avoid brute forcing it by doing

private Dictionary<CourseGrades, string> _courseGradesWithCaption = null;
public Dictionary < CourseGrades, string > CourseGradesWithCaptions
{
    get
    {
        if ( _courseGradesWithCaption == null )
            _courseGradesWithCaption = new Dictionary < CourseGrades, string > ()
            {
                { CourseGrades.A, CourseGrades.A.GetDescription () }
                , { CourseGrades.AMinus, CourseGrades.AMinus.GetDescription () }
                , { CourseGrades.BPlus, CourseGrades.BPlus.GetDescription () }
                // ... snip ...
            };
        return _courseGradesWithCaption;
    }
}

I thought I was getting somewhere by borrowing how the extension method linked above went through the enumeration by doing

public static class EnumerationCaptionsBuilder
{
    public static Dictionary < T, string > BuildCaptions<T> ( T e ) where T : IConvertible
    {
        if (!(e is System.Enum)) throw new ArgumentException("e is expected to be an enumeration.");

        var type = e.GetType ();
        var values = System.Enum.GetValues ( type );
        var dict = new Dictionary<T, string> ();


        foreach ( var val in values )
        {
            if ( (int) val != e.ToInt32 ( CultureInfo.InvariantCulture ) ) continue;

            var enumElement = type.GetMember ( type.GetEnumName ( val ) ?? throw new InvalidOperationException ());

            dict.Add(enumElement, enumElement.GetDescription());
        }
    }
}

But that was when I learned that type.GetMember returns a MemberInfo object which is not what I am looking for.

Is there a way to do what I am wanting to do or am I just barking up an impossible tree?

Upvotes: 0

Views: 320

Answers (1)

N_tro_P
N_tro_P

Reputation: 693

I am not totally following why your extension doesn't work for you (if you have it on the Enum your "requested" code should work).

Here is how I have done this though:

public static class EnumerationExtensions
{
    public static string GetDescription(this Enum value)
    {
        FieldInfo fi = value.GetType().GetField(value.ToString());

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

        return attributes.Length > 0 ? attributes[0].Description : value.ToString();
    }
    public static IEnumerable<TEnum> GetEnumCollection<TEnum>(bool skipFirst = false)
    {
        var result = new List<TEnum>();
        var enums = Enum.GetValues(typeof(TEnum));
        for (int i = 0; i < enums.Length; i++)
        {
            if (skipFirst && i == 0) continue; //Some enums use "Invalid" or "Default" as first value
            TEnum e = (TEnum)enums.GetValue(i);
            result.Add(e);
        }
        return result;
    }
}

Also, one thing to think about is why are you needing it. If this is for display purposes, perhaps a IValueConverter is what you should do. This allows you to have a collection of enumeration types and then display the description quite easy as the IValueConverter object receives the enum and then returns the .Description extension call.

The example I provided has a "skipFirst" defaulted to false, because often when I make enumerations I put an invalid sate first (e.g. Default, Undefined etc.) to ensure default state is not valid (I want to ensure something is set and not use a default).

EDIT Adding the IValueConverter that I have used.

public class EnumerationToDescriptionConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var casted = value as Enum;
        return casted?.GetDescription();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        //we have no need to get from visual descirption to the enumeration at this time.
        throw new NotImplementedException();
    }
}

Then you just need to instantiate your converter as a resource with a key and reference it.

<converters:EnumerationToDescriptionConverter x:Key="EnumerationToDescriptionConverter" />

<TextBlock Text="{Binding Converter={StaticResource EnumerationToDescriptionConverter}}"/>

In my case the TextBlock is within a DataTemplate used for a ItemsControl that is bound to a collection of the enumerations that are retrieved via the other extension method posted above.

Upvotes: 1

Related Questions