Friday, June 22, 2012

List all values of an enumeration

I'm probably late to the party on this one, but I recently discovered a cool, handy little static method tucked away in the Enum class: GetValues. It simply returns all the values of an enum type. This enables nifty things like:
public enum TestEnum
{
    Value1,
    Value2,
    Value3,
    Value4
};

public void ShowEnumValues()
{
    Enum.GetValues(typeof (TestEnum))
        .Cast<TestEnum>()
        .ToList()
        .ForEach(e => Console.WriteLine(e));
}
With this, any changes to TestEnum automatically get picked up at run-time without any additional changes to the rest of the code.

Last year, I wrote about using a dictionary to facilitate converting between an enum and human readable text. To adapt the code presented then to the above example, the following will convert all values in the enum to human readable text.
private IDictionary<TestEnum, string> enumToString =
    new Dictionary<TestEnum, string>
        {
            {TestEnum.Value1, "Value one"},
            {TestEnum.Value2, "Value two"},
            {TestEnum.Value3, "Value three"},
            {TestEnum.Value4, "Value four"}
        };

public void ShowEnumStrings()
{
    Enum.GetValues(typeof(TestEnum))
        .Cast<TestEnum>()
        .ToList()
        .ForEach(e => Console.WriteLine(enumToString[e]));
}

In the previous article, I lamented the fact that this C# solution, unlike the Pascal variant, does not provide a reliable means to check that all the values of the enumeration have a corresponding entry in the dictionary. Because of strong typing with generics, I know I can't have any thing that's not part of the enumeration in the dictionary, but I can't know I have all the values of the enumeration in the dictionary. In other words, I know I can't have any invalid values, but I can't know I have all the values.

Until I found this method. Now I can at least write an easy test case for this. While it's not quite as good as a compile time check, it does give an early, reliable means of being alerted to the problem.
[TestMethod]
public void EnsureAllEnumValuesAreInDictionary()
{
    Assert.IsTrue(
        Enum.GetValues(typeof(TestEnum))
            .Cast<TestEnum>()
            .All(type => EnumToString.ContainsKey(type)),
        "Enum value missing from enumToString dictionary");
}

Since variables of this type typically do not change from instance to instance, they can be made static so they're only instantiated once. And since many times they are only used inside a single class, they can be made private. In these cases having a unit test might be problematic, not being able to test a private field. To solve this, code similar to the above test can be put in a static constructor, throwing an exception if there's a mismatch.
private static readonly IDictionary<TestEnum, string> EnumToString =
    new Dictionary<TestEnum, string>
        {
            {TestEnum.Value1, "Value one"},
            {TestEnum.Value2, "Value two"},
            {TestEnum.Value3, "Value three"},
            {TestEnum.Value4, "Value four"}
        };

static UnitTest1()
{
    if (!Enum.GetValues(typeof (TestEnum))
             .Cast<TestEnum>()
             .All(type => EnumToString.ContainsKey(type)))
        throw new InvalidOperationException(
            "Enum value missing from enumToString dictionary");
}

Finally, if this needs to be done much, making a generic helper function will clean-up code, hiding all the casting and repetition of the type.
private static IEnumerable<T> GetEnumValues<T>()
{
    return Enum.GetValues(typeof (T)).Cast<T>();
}

[TestMethod]
public void EnsureAllEnumValuesAreInDictionary1()
{
    Assert.IsTrue(
        GetEnumValues().All(type => EnumToString.ContainsKey(type)),
        "Enum value missing from enumToString dictionary");
}

All in all, I'm pleased with finding this little nugget.

A complete VS2008 solution containing the code examples above is available on github.

Hope this helps someone else.

No comments: