Every couple of years, I tend to write the same variation of an enum helper that reads metadata from an enumeration using reflection. Almost any .NET developer with a few years of experience has done the same. The implementation uses Generics to work for any enum defined in my solution, as there is typically more than one described in a typical .NET solution. Historically, generics and enums didn’t work well because of the limitations of generics when dealing with this particular type, but to my surprise, they do now!

In this post, we’ll explore how to write the helper method I’m describing while ensuring the generic constraints stop us from passing in arguments other than enums.

Straight To The Implementation

Since this post will be short, I’ll start with a sample you can play with.

using System.ComponentModel;
using System.Reflection;

// get all descriptions
{
    Console.WriteLine("Groceries:\n");
    var descriptions = Enums.GetDescriptions<Groceries>();
    foreach (var (value, description) in descriptions)
    {
        Console.WriteLine($"{value} - {description}");
    }
    Console.WriteLine();
}

// Get a description for a single value
{
    var (value, description) = Enums.GetDescription(Groceries.Fruit);
    Console.WriteLine($"Single value:\n{value} - {description}");
}

public enum Groceries
{
    [Description("Apples, Oranges, Bananas")]
    Fruit,
    [Description("Spinach, Kale, Broccoli, Cabbage")]
    Vegetables,
    [Description("Cheese, Milk, Yogurt")]
    Dairy,
    [Description("Chicken, Beef, Pork, Lamb, Turkey")]
    Meat,
    [Description("Anything not listed above")]
    Other
}

public static class Enums
{
    public static IEnumerable<(TEnum Value, string Description)> GetDescriptions<TEnum>()
        where TEnum : struct, Enum
    {
        var values = Enum.GetValues<TEnum>();
        foreach (var value in values)
        {
            yield return GetDescription(value);
        }
    }

    public static (TEnum Value, string Description) GetDescription<TEnum>(TEnum value)
        where TEnum : struct, Enum
    {
        var type = typeof(TEnum);
        var name = value.ToString();
        var info = type.GetMember(name)[0];
        var description = info.GetCustomAttribute<DescriptionAttribute>() is { } attr
            ? attr.Description
            : name;
        
        return (value, description);
    }
}
C#

The helper code is here for people who don’t want to visualize the previous sample.

public static class Enums
{
    public static IEnumerable<(TEnum Value, string Description)> GetDescriptions<TEnum>()
        where TEnum : struct, Enum
    {
        var values = Enum.GetValues<TEnum>();
        foreach (var value in values)
        {
            yield return GetDescription(value);
        }
    }

    public static (TEnum Value, string Description) GetDescription<TEnum>(TEnum value)
        where TEnum : struct, Enum
    {
        var type = typeof(TEnum);
        var name = value.ToString();
        var info = type.GetMember(name)[0];
        var description = info.GetCustomAttribute<DescriptionAttribute>() is { } attr
            ? attr.Description
            : name;
        
        return (value, description);
    }
}
C#

The essential part of our generic methods is our method declarations’ where clause.

where TEnum: struct, Enum
C#

This line adds two crucial elements to our generic method.

It makes sure that TEnum is an Enum type. The struct ensures that TEnum is non-nullable.

The non-nullability of an Enum is essential since we are using the Enum.GetValues<TEnum> method to get known values. You could remove this constraint, but your implementation would require more boxing and defensive programming in the form of null checks. It’s more straightforward to enforce the struct requirement.

Conclusion

There you go, a constrained generic method implementation that can get metadata from an Enum value. If you’re still wondering, “where would I use this?” I find these methods helpful in web applications for creating a SelectListItem, but I stuck with Tuples for this post. What a time to be alive. Cheers!