Jun 8, 2008
Emulating Java 5 enums in .NET: Part 2
Last time, I looked at how to emulate Java 5 enums on the .NET 2 runtime. This time I’m going to allow myself to take advantage of the new features supported in the .NET 3.5 runtime.
Reminder: what we’re trying to emulate
Java 5 enums allow you to enhance your enums with data and behaviour. We’re trying to recreate the Planet example in .NET:
public enum Planet {
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6),
JUPITER (1.9e+27, 7.1492e7),
SATURN (5.688e+26, 6.0268e7),
URANUS (8.686e+25, 2.5559e7),
NEPTUNE (1.024e+26, 2.4746e7),
PLUTO (1.27e+22, 1.137e6);
private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
public double mass() { return mass; }
public double radius() { return radius; }
// universal gravitational constant (m3 kg-1 s-2)
public static final double G = 6.67300E-11;
public double surfaceGravity() {
return G * mass / (radius * radius);
}
public double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
}
Extension Methods
One important new .NET feature is extension methods. Extension methods allow you to augment existing classes with new member methods. For example, the following code adds a method to DateTime instances to convert them to the Unix time format (the number of seconds since the Unix epoch, 1970-01-01 0:00:0.000):
using System;
namespace MyExtensionMethods
{
public static class DateTimeExtensions
{
private static readonly DateTime UnixEpoch
= new DateTime(1970, 1, 1, 0, 0, 0, 0, 0);
public static long ToUnixTime(this DateTime date)
{
return (long)(date.ToUniversalTime() - UnixEpoch).TotalSeconds;
}
}
}
The extension method is then visible on any instance of a DateTime object once you have imported the namespace:
using MyExtensionMethods;
public class UnixTimeTests
{
public void Test_UnixTimeExtensionMethod()
{
DateTime now = DateTime.Now;
long unixNow = now.ToUnixTime();
}
}
You even get Intellisense!
Planet enum with Extension Methods
The plan is very simple: attach extension methods to our raw Planet enum type. Ironically, the Mass and Radius properties have to be implemented as getter methods, just as in Java, since we can’t add extension properties:
public enum Planet
{
Mercury,
Venus,
Earth,
Mars,
Jupiter,
Saturn,
Uranus,
Neptune,
Pluto
}
public static class PlanetExtensions
{
public static double GetMass(this Planet planet)
{
switch (planet)
{
case Planet.Mercury: return 3.303e+23;
case Planet.Venus: return 4.869e+24;
case Planet.Earth: return 5.976e+24;
case Planet.Mars: return 6.421e+23;
case Planet.Jupiter: return 1.9e+27;
case Planet.Saturn: return 5.688e+26;
case Planet.Uranus: return 8.686e+25;
case Planet.Neptune: return 1.024e+26;
case Planet.Pluto: return 1.27e+22;
default: throw new Exception("Illegal state");
}
}
public static double GetRadius(this Planet planet)
{
switch (planet)
{
case Planet.Mercury: return 2.4397e6;
case Planet.Venus: return 6.0518e6;
case Planet.Earth: return 6.37814e6;
case Planet.Mars: return 3.3972e6;
case Planet.Jupiter: return 7.1492e7;
case Planet.Saturn: return 6.0268e7;
case Planet.Uranus: return 2.5559e7;
case Planet.Neptune: return 2.4746e7;
case Planet.Pluto: return 1.137e6;
default: throw new Exception("Illegal state");
}
}
public const double G = 6.67300E-11;
public static double GetSurfaceGravity(this Planet planet)
{
return G * planet.GetMass() / (planet.GetRadius() * planet.GetRadius());
}
public static double SurfaceWeight(this Planet planet, double otherMass)
{
return otherMass * planet.GetSurfaceGravity();
}
}
This comes pretty close to being a perfect solution. We haven’t changed the enum itself so you can use it just as if there were no extra functionality; there’s no funky switch syntax, for example. There’s still room for improvement; I don’t like having the data so far from the enum member declaration. Yet, we’ve implemented the full functionality from the Java 5 example, and I think that’s testament to the flexibility of C#.
What do you think?
The source code for this experiment is available at Google Code as a Visual Studio 2008 project, licensed under a MIT license.