Fake Java enums using C# extension methods

No Comments March 25, 2009

One of the nice features introduced into Java 1.5 that is missing in C# is enum types. In C#, enums are nothing more than a defined set of values with labels, whereas in Java enums can contain methods. I've seen a few attempts to mimic Java enums in C#, but most solutions seem to rely on Attributes to decorate the enum which is fine except that in relative terms, reflection operations are expensive.

C# 3.0 introduced the concept of extension methods which enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. As is my wont, I was thinking about this in the shower this morning when I had a eureka moment - surely it should be possible to fake Java enums using C# extension methods?

Using the Java planets example, it didn't take long to whip up the following:

using System;
using System.Collections.Generic;

namespace ScratchPad
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var p = new Program();
            p.Run();
        }

        private void Run()
        {
            double earthWeight = 175;
            double mass = earthWeight / Planet.Earth.SurfaceGravity();

            foreach (Planet planet in Enum.GetValues(typeof(Planet))) {
                Console.WriteLine("Your weight on {0} is {1}", planet, planet.SurfaceWeight(mass));
            }
        }
    }

    public enum Planet
    {
        Mercury,
        Venus,
        Earth,
        Mars,
        Jupiter,
        Saturn,
        Uranus,
        Neptune
    }

    public static class PlanetExtensions
    {
        private static readonly Dictionary planetMap = new Dictionary
          {
              {Planet.Mercury, new PlanetData(3.303e+23, 2.4397e6)},
              {Planet.Venus, new PlanetData(4.869e+24, 6.0518e6)},
              {Planet.Earth, new PlanetData(5.976e+24, 6.37814e6)},
              {Planet.Mars, new PlanetData(6.421e+23, 3.3972e6)},
              {Planet.Jupiter, new PlanetData(1.9e+27,   7.1492e7)},
              {Planet.Saturn, new PlanetData(5.688e+26, 6.0268e7)},
              {Planet.Uranus, new PlanetData(8.686e+25, 2.5559e7)},
              {Planet.Neptune, new PlanetData(1.024e+26, 2.4746e7)}
          };

        private const double G = 6.67300E-11;

        public static double Mass(this Planet planet)
        {
            return GetPlanetData(planet).Mass;
        }

        public static double Radius(this Planet planet)
        {
            return GetPlanetData(planet).Radius;
        }

        public static double SurfaceGravity(this Planet planet)
        {
            PlanetData planetData = GetPlanetData(planet);

            return G * planetData.Mass / (planetData.Radius * planetData.Radius);
        }

        public static double SurfaceWeight(this Planet planet, double mass)
        {
            return mass * SurfaceGravity(planet);
        }

        private static PlanetData GetPlanetData(Planet planet)
        {
            if (!planetMap.ContainsKey(planet))
                throw new ArgumentOutOfRangeException("planet", "Unknown Planet");

            return planetMap[planet];
        }

        #region Nested type: PlanetData

        public class PlanetData
        {            
            public PlanetData(double mass, double radius)
            {
                Mass = mass;
                Radius = radius;
            }

            public double Mass { get; private set; }
            public double Radius { get; private set; }
        }

        #endregion
    }
}

It's all very simple; the only drawback is that you must remember to match any changes to your enum in the associated extension class. Overall, I think this is a pattern I might start to use more often.


No Comments