Wednesday, August 30, 2006

To me, enums in the .NET Framework 2.0 don’t feel like a polished feature. Writing serious code with them is a descent into typecasting madness filled with the maniacal laughter of bitwise ands and ors. Maybe the problem is that they are considered "good enough" or simply "not interesting enough" to improve. I mean, what’s more exciting to a compiler writer: enumerations or generics and lambda expressions? It’s possible that enums haven’t been improved simply because of time constraints or resources. But, in my humble opinion, it’s high time they deserved some love.

In this article, I offer up my list of potential improvements for .NET enums. When possible, I present workarounds to implement the improvements with the current or next version of the .NET framework.

Implicit Conversion to Underlying Type

Every enum has an underlying type that is one of the following: System.Byte, System.Int16, System.Int32, System.Int64, System.SByte, System.UInt16, System.UInt32 or System.UInt64. This underlying type is used to store the actual numeric value of the enum. By default, the underlying type is System.Int32 but a different underlying type can be chosen when defining an enum:

enum Fruit: byte { Apple, Banana, Orange }

If I want to convert a value from the above enum, I have to typecast:

byte theFruit = (byte)Fruit.Banana;

Because Fruit is explicitly declared to have an underlying type of System.Byte, it seems redundant to the extreme to require a typecast to a variable of that same type. Especially, when implicit conversions are available in other situations where a numeric value is guaranteed to fit inside another with no loss of precision:

byte b = 42;
int i = b; // no typecast because int is large enough to hold any byte.

I propose that enums are, at the very least, made implicitly convertible to their underlying type. When converting from the underlying type to the enum, it seems good practice and protective to require a typecast in case the enum doesn’t define the appropriate named value.

byte theFruit = Fruit.Banana; // no typecast
Fruit otherFruit = (Fruit)4; // typecast required (Fruit doesn’t have a named value for 4)

I’m sure that the type safety police will be out in force on me for this but this seems dogmatic just for the sake of being dogmatic. Some might argue that a typecast improves readability in the above code because the type is visible on both sides of the assignment but I would argue that the typecast actually adds visual noise and makes it harder to read.

Generic Constraint

Currently, there is no way to constrain a generic type parameter to an enum. In fact, it is illegal to constrain generic type parameter to inherit from System.Enum. So, neither of these method declarations is legal:

static class EnumHelpers
{
  public static T[] GetValues<T>()
    where T: enum;
  public static T[] GetValues<T>()
    where T: System.Enum;
}

We can constrain the generic type parameter to a struct since all enums implicitly inherit from System.ValueType but runtime code must be written to further constrain it to an enum:

static class EnumHelpers
{
  public static T[] GetValues<T>()
    where T: struct
  {
    if (!typeof(T).IsEnum)
      throw new ArgumentException("Type argument 'T' must be an enum.");

    return (T[])Enum.GetValues(typeof(T));
  }
}

The above code works fine except that it fails to generate a compile time error because any struct can specified for T. So, this method will compile but fail spectacularly if called like this:

int[] values = EnumHelpers.GetValues<int>();

In my opinion, the biggest benefit we would get from a new enum constraint for generics is the possibility of a long overdue update to the System.Enum class. Currently, every public static method on System.Enum declares its first parameter as a System.Type. Each method has checks on this parameter to determine whether it is an enum and throw the appropriate exception if it isn’t. In addition, any method that returns an enum value or takes an enum value as a parameter uses System.Object. This causes unnecessary boxing and unboxing operations which seem really wasteful considering that enums are supposed to be lightweight.

Generic versions of the public static methods on System.Enum with an enum constraint could remove the System.Type parameter and the runtime checks associated with it. This allows client code to remove cumbersome typeof() operators. Also, because no exceptions would be thrown, try/catch handlers could be removed from code that calls these methods. In addition, the boxing and unboxing operations would be removed as well as any previously required typecasts in calling code. In essence, there would be benefits in both performance and code readability.

Today, we can achieve improved code readability by creating our own class that wraps the System.Enum static methods with generic methods. Such a class actually reduces performance slightly because it is another layer of indirection between the client code and System.Enum. But, 99% of the time, readability would be preferred over this minor performance hit.

Compiler-Generated Static Methods

In addition to updating the static methods on System.Enum, it would be useful for the compiler to generate the necessary public static methods on the enum itself. Compiler-generated methods aren’t a new concept to the .NET Framework. For example, BeginInvoke, EndInvoke and Invoke methods are generated for delegates. I propose generating all of the static methods that appear on System.Enum onto all new enums at compile time. With this in place, code could be written like this:

using System;

static class Program
{
  enum Day { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };

  static Main(string[] args)
  {
    Array.ForEach(Day.GetNames(), Console.WriteLine);
  }
}

With compiler-generated static methods on enums, users could forget about System.Enum completely.

Please note that any enum inherits the static methods of System.Enum (even though they don’t appear in Intellisense). So, the above code could be rewritten like this:

using System;

static class Program
{
  enum Day { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };

  static Main(string[] args)
  {
    Array.ForEach(Day.GetNames(typeof(Day)), Console.WriteLine);
  }
}

However, passing “typeof(Day)” into a method on the “Day” type seems pretty redundant to me.

Instance Methods

Giving developers the ability to declare instance methods on enums allow any utility functions that operate on a particular enum to be place on the enum itself. Here’s what I have to write today:

using System;

static class Program
{
  enum Day { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };

  static bool IsWeekday(Day day)
  {
    return (day != Day.Sunday && day != Day.Saturday);
  }

  static void Main(string[] args)
  {
    foreach (Day day in Enum.GetValues(typeof(Day)))
      Console.WriteLine("{0,10} = {1}", day, IsWeekday(day));
  }
}

Here’s what I would like to be able to write:

using System;

static class Program
{
  enum Day
  {
    Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday;

    public bool IsWeekday()
    {
      return (this != Day.Sunday && this != Day.Saturday);
    }
  };

  static void Main(string[] args)
  {
    foreach (Day day in Enum.GetValues(typeof(Day)))
      Console.WriteLine("{0,10} = {1}", day, day.IsWeekday());
  }
}

Fortunately, there is a workaround if I have the LINQ preview installed. With a little extra code, I can simulate instance methods using C# 3.0’s extension method feature:

using System;

static class Program
{
  enum Day { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };

  static class DayMethods
  {
    public static bool IsWeekday(this Day day)
    {
      return (day != Day.Sunday && day != Day.Saturday);
    }
  };

  static void Main(string[] args)
  {
    foreach (Day day in Enum.GetValues(typeof(Day)))
      Console.WriteLine("{0,10} = {1}", day, day.IsWeekday());
  }
}

Using an extension method, I can achieve exactly the same client code but I have to do a little bit more coding and create a new class to hold the method. It seems to me that the C# team could add instance methods to enums for C# 3.0 by generating extension methods at compile time.

Where Are We?

To many, enums are perfectly satisfactory as they are. There really is very little that can't do with an enum today. But, if you want to code them with elegance and style, they'll need a bit of love first.

posted on Wednesday, August 30, 2006 10:49:47 AM (Pacific Standard Time, UTC-08:00)  #    Comments [1]

kick it on DotNetKicks.com
Saturday, July 14, 2007 4:29:04 PM (Pacific Standard Time, UTC-08:00)
Hey,

I haven't tried this out but with a C# 3.0 compiler you could use extension methods to achieve some functionality:

public static class EnumExtensions
{
public static string[] GetNames(this System.Enum enum)
{
return Enum.GetNames(typeof(enum));
}
}

I have seen System.Enum behave weird when it came to the enums inheriting from them. I'm not sure this method eventually shows up on public enum Days { ... };. If yes, cool. If not, let's hope they improve enums in the next iteration.

I like your idea of the implicit cast from the enum to the underlying type. I've also seen that you have Orcas beta1. Don't forget that the C# 3.0 compiler (without using LINQ, which is .NET FW 3.5) produces .NET 2.0 assemblies as the CLR did not change between 2.0 and 3.0.
Gabor Ratky
Comments are closed.