Friday, January 14, 2011

Enum subsets in C#

After a few years of working with Haskell I've become nearly obsessed with expressing constraints in the type system. Although the C# type system isn't as expressive as Haskell's, preconditions, postconditions, ranges, complexity, and other things can be expressed. If the type is expressive enough then you don't even have to do any validation or checking when working with it because the compiler already guaranteed that the data is valid. One of the problems with C# enums is that they aren't real objects so they can't subclass; they're just pretty wrappers around primitives like ints or bytes. This means that if you only allow certain values of an enum then you have to manually check at runtime:

public void DeclareDayIsSpecial(DayOfWeek day)
{
  if (day == DayOfWeek.Sunday || day == DayOfWeek.Saturday)
    throw new ArgumentException("When declaring that a day is special, the day must be a weekday.", "day");
 
  Console.WriteLine("{0} is special.", day);
}

Even though you can't define an enum as a subclass of another and allow only that subclass, you can define an enum as subset of another.

public enum Weekday
{
   Monday = DayOfWeek.Monday,
   Tuesday = DayOfWeek.Tuesday,
   Wednesday = DayOfWeek.Wednesday,
   Thursday = DayOfWeek.Thursday,
   Friday = DayOfWeek.Friday,
}

Once you have this you can constrain your methods to just the valid subset of values.

public void DeclareDayIsSpecial(Weekday weekday)
{
  Console.WriteLine("{0} is special.", weekday);
}

The compiler won't allow you to use an invalid value so you have compile time documentation and feedback and can remove the runtime checks. This can be useful if you use enums in a scenario where only some values are valid. And since enums are wrappers around primitive types, you can safely cast from the subset (Weekday) to the superset (DayOfWeek).

1 comment: