Polymorphic enums in C# ?

Using Java as a inspiration source for C# (hmm wait, hasn’t that already been done from the start ?)

A few weeks ago, I read this post by @cyriux, describing how you could use polymorphic enums in Java. Actually, I had already heard him telling that enums in C# were nothing like the ones in Java. And even if he’s sometimes trolling, he’s really got a point there. Then I thought to myself : I know I won’t be able to add this syntax to C#, because I can’t change the compiler, but how clause could I get to polymorphic enums ? Let’s try that…

First, what are the common characteristics of an enum ?

  • enums bind together a scalar value and a string representation, so enums can be converted to and parsed from strings
  • for the same reason, enums can be casted to and from scalar values (int, long, short…),
  • enums should be immutable (in fact, unless you introduce some mutable state in them, they already are),
  • enums cannot be extended by other types : once defined, their set of values is closed and cannot be modified.

I haven’t checked for Java, but here are some other characteristics of C# enums :

In order to build a type that behaves like an enum, I’ve made the following decisions :

  • I want to build a base type from which actual enums will derive. As there is nothing in C# as an abstract struct (value type), I have to implement a class and not a struct.
  • In order to obtain a value type behaviour (value comparison instead of instance equality), I’ll will have to implement it.
  • I will implement interfaces IComparable and IConvertible, but I haven’t really seen any use case where I would use IFormattable, so I chose not to implement it.
  • Enums in C# don’t have an Ordinal property, but I’ll introduce one, so that I can write unit tests as close as possible as @cyriux’s ones. It will look a little bit Java-ish… but of course I’ll keep the C# casing convention.
  • The underlying type used to represent an enum is an integer by default, but other types can be used, such as a long or an unsigned short. I will try to make the enums definitions as short a possible, so I’ll use generics and take advantage of the type inference system.

My C# enum has behaviour

Enough text there ! Directly inspired from @cyriux’s post’s first sample “Rock – paper – scissors”, here is the test cases that I want to execute :

[TestMethod]
public void paper_beats_rock()
{
    Assert.IsTrue(Gesture.PAPER.Beats(Gesture.ROCK));
    Assert.IsFalse(Gesture.ROCK.Beats(Gesture.PAPER));
}

[TestMethod]
public void scissors_beats_paper()
{
    Assert.IsTrue(Gesture.SCISSORS.Beats(Gesture.PAPER));
    Assert.IsFalse(Gesture.PAPER.Beats(Gesture.SCISSORS));
}

[TestMethod]
public void rock_beats_scissors()
{
    Assert.IsTrue(Gesture.ROCK.Beats(Gesture.SCISSORS));
    Assert.IsFalse(Gesture.SCISSORS.Beats(Gesture.ROCK));
}

And here is the implementation of the enum, built upon my PolymorphicEnum class :

/** PolymorphicEnum have behavior! **/
public class Gesture : PolymorphicEnum<Gesture>
{
    public static Gesture ROCK = Register<RockGesture>();
    public static Gesture PAPER = Register();
    public static Gesture SCISSORS = Register();

    // we can implement with the integer representation
    public virtual bool Beats(Gesture other)
    {
        return this.Ordinal - other.Ordinal == 1;
    }

    private class RockGesture : Gesture
    {
        public override bool Beats(Gesture other)
        {
            return other == SCISSORS;
        }
    }
}

As you can see in the implementation, it is now possible to define a Gesture enum, that exposes 3 static properties of type Gesture. One of these properties, ROCK, is in fact an instance of RockGesture, which is a Gesture that overrides the behaviour of Beats method.

Strategy Pattern

All the Java sample from the post can now be ported to C# ! The next one was the Eurovision contest, where an abstract method was defined in the base enum, and overridden in each enum instance, in order to implement a strategy pattern. This is where I admit I couldn’t get the same behaviour, as for internal implementations reasons I needed my base class to not be abstract… So I had to define a default behaviour :

public class EurovisionNotification
    : PolymorphicEnum<EurovisionNotification>
{
    /** Default behavior : I don't want to know ! */
    public virtual bool MustNotify(String eventCity,
                                   String userCity)
    {
        return false;
    }

    /** I love Eurovision, don't want to miss it, never! */
    private class EurovisionNotificationAlways
        : EurovisionNotification
    {
        public override bool MustNotify(string eventCity,
                                        string userCity)
        {
             return true;
        }
    }

    /**
     * I only want to know about Eurovision if it takes place
     * in my city, so that I can take holidays elsewhere at
     * the same time
     */
    private class EurovisionNotificationOnlyCity
        : EurovisionNotification
    {
        public override bool MustNotify(string eventCity,
                                        string userCity)
        {
             return eventCity.Equals(
                userCity,
                StringComparison.InvariantCultureIgnoreCase);
        }
    }

    public static EurovisionNotification ALWAYS =
        Register<EurovisionNotificationAlways>();
    public static EurovisionNotification ONLY_IN_MY_CITY =
        Register<EurovisionNotificationOnlyCity>();
    public static EurovisionNotification NEVER =
        Register();
}

State Pattern

The next sample is the one where a baby is represented as a state machine :

public class BabyState : PolymorphicEnum<BabyState>
{
    public static BabyState POOP = Register();

    public static BabyState SLEEP =
        Register(data: new { Next = POOP });

    public static BabyState EAT =
        Register(data: new { Next = SLEEP });

    public static BabyState CRY =
        Register(data: new { Next = EAT });

    public BabyState Next(bool discomfort)
    {
        if (discomfort)
            return CRY;

        return this.Data == null
            ? EAT
            : (BabyState)((dynamic)this.Data).Next;
    }
}

Just as for the Java enums, “we can reference enum constants between them, with the restriction that only constants defined before can be referenced”. The workaround here, as @cyriux wrote in his post, is to open the cycle. This builds allows us to build the graph on the right.

We can notice here that in order to embed data in an enum value, I have introduced an extra parameter “data” in the Enum method, which is responsible for the Enum values registration. Using this extra parameter, we can go back to the Eurovision sample and rewrite this way :

using Policy = System.Func<string, string, bool>;

public class EurovisionNotificationLambda
    : PolymorphicEnum<EurovisionNotificationLambda>
{
    private static Policy Policy(Policy f) { return f; }

    public static EurovisionNotificationLambda ALWAYS =
        Register(data: Policy((eventCity, userCity) => true));

    public static EurovisionNotificationLambda ONLY_IN_MY_CITY =
        Register(data: Policy((eventCity, userCity) =>
            eventCity.Equals(
                userCity,
                StringComparison.InvariantCultureIgnoreCase)));

    public static EurovisionNotificationLambda NEVER =
        Register(data: Policy((eventCity, userCity) => false));

    public bool MustNotify(String eventCity, String userCity)
    {
        return ((Policy)this.Data).Invoke(eventCity, userCity);
    }
}

In this implementation, each enum value owns its notification policy, in the form of a types delegate, which is declared using a lambda expression.

Enum-non-optimized collections

Ok, I haven’t gone so far as implementing optimized collections that take advantage of the integer nature of enums ! So not all the arguments listed by @cyriux apply to my C# implementation… But I still get polymorphism, the correct Equals, GetHashCode, ToString, string or integer serialization, and singleton-ness for free, with very little code ! Feel free to experiment with it if you want, the whole project will be available on my GitHub very soon.

In a next post, I’m going to talk about some points of interest in my PolymorphicEnum implementation.

This entry was posted in Syntax Puzzles and tagged , , , . Bookmark the permalink.

Comments are closed.