C# Programming > Miscellaneous

Enum To Readable String

 

Enum Strings

Enums in C# are extremely useful structures that help create code that is easier to read and maintain. However one of the most common complaints is that turning an enum to a human readable string and parsing back is not very easy. Here we'll take a look at how we can make this possible.

Update (12/11/11): ignoreCase parameter instead of caseSensitive. Use Enum.TryParse instead of searching through each name.

Enum Info

A quick overview of the anatomy of an enum type. Since we are going to use reflection and generics, it is important to know all enums derive from the base Enum which is a structure and each of the possible values is a public field. Knowing those two things will make it possible to use reflection on enums.

Attributes

To make this easy, we are going to leverage attributes. This will let us indicate what the readable string version of each enum value is along side the value declaration. So the final result looks something like this:

private enum Numbers
{
    [ReadableString("One Hundred")]
    [ReadableString("100")]
    OneHundred
}

A popular approach is to use the DescriptionAttribute class. However for our approach we are going to write our own simple attribute class (ReadableStringAttribute). Why? Because we can't use more than on Description attribute.

To Readable String

To take an enum value (Numbers.OneHundred for example) and turn it into a readable string, we need to use .NET reflection to find any associated ReadableString attributes and return the value of the first one (for simplicity). If there is not attribute we can either throw an exception or return a the result of ToString as default.

public static string ToReadableString(this Enum en)
{
    string str = en.ToString(); //by default fall back to regular string

    //Find the first readable string attribute
    Type enumType = en.GetType();
    FieldInfo currentValue = enumType.GetField(en.ToString());

    object[] attributes = currentValue.GetCustomAttributes(typeof(ReadableStringAttribute), inherit: false) ?? new object[0];
    if (attributes.Length > 0)
        str = ((ReadableStringAttribute)attributes[0]).ReadableString;

    return str;
}

This funciton can be an extension function since an instance of the base class Enum referes to a particular enum value (Numbers.OneHundred as opposed to just Numbers).

Parse Readable String

Taking a readable string and returning the matching enum value is interesting (for example, reading "One Hundred" or "100" and returning Numbers.OneHundred). To make this work for all enums we can use generics. Note that C# does not let us limit the generic type to an Enum, but since an Enum is a structure, we can limit T to struct.

public static T ParseReadableString<T>(string str, bool ignoreCase = false) where T : struct //Enum
{
    if (!typeof(T).IsEnum)
        throw new ArgumentException("T must be an enum", "T");

    FieldInfo[] values = typeof(T).GetFields() ?? new FieldInfo[0];

    //Find a value (field) of enum T that has a ReadableString attribute matching the input string
    foreach (FieldInfo value in values)
    {
        object[] attributes = value.GetCustomAttributes(typeof(ReadableStringAttribute), inherit: false) ?? new object[0];
        foreach (object attr in attributes)
        {
            string readableStr = ((ReadableStringAttribute)attr).ReadableString;
            if (string.Compare(str, readableStr, ignoreCase) == 0)
                return (T)value.GetValue(null);
        }
    }

    //No match found, try to find one based on the name instead
    T parsedValue = default(T);
    bool matchFound = Enum.TryParse<T>(str, ignoreCase, out parsedValue);
    if (matchFound)
        return parsedValue;
    else
        throw new ArgumentException("Unknown value " + str, "str");
}

The main idea is to go through every value (field) of an enum and check every ReadableString attribute it has. If one matches, the value is returned. This is where having multiple ReadableString attributes is useful since we can parse several strings to a single enum value. If no match is found, we look directly at the enum name (OneHundred for example).

Furthermore, since we are comparing strings one by one, we can take this chance to allow parsing strings while ignoring case. This way "one hundred" and "One Hundred" are treated as the same thing. This is extremely useful in some contexts.

So we can parse strings to an enum like so:

Numbers num = ParseReadableString<Numbers>("One Hundred");
//or
Numbers num = ParseReadableString<Numbers>("one hundred", ignoreCase: true);
//or
Numbers num = ParseReadableString<Numbers>("100");

Conclusion

We can combine all of the above into a nice helper class. The ReadableStringAttribute can either be part of the helper class or be separate. In the downloadable C# code below I left them separate since it makes for slightly cleaner code.

Back to C# Article List