Enum Polymorphism

There are times in code when you if available, enum polymorphism would be great. To help explain what I mean by this, consider the following scenerio.

Let’s define a simple library that allows a client to read and write strings to addresses. Some of these addresses are read only, whilst others may be both read and written.

public enum Address : byte
{
    // A read-only serial number (read)
    SerialNumber = 0x01,

    // A user-defined name (read and write).
    Name = 0x02
}

public static string Read(Address address);
public static void Write(Address address, string value);

Then a client can use this library as shown below.

static void Main(string[] args)
{
    // All good, can read from both addresses
    string serial_number = Read(Address.SerialNumber);
    string name = Read(Address.Name);

    // Good - can write to name address
    Write(Address.Name, "john smith");

    // BAD - run-time error!
    Write(Address.SerialNumber, "0123456789");
}

 

Run-Time vs Compile-Time Errors

This program will compile successfully, but will fail at run-time (when we attempt to write to the SerialNumber address). Instead, it would be better if we could force the compiler to help us find these errors.

For this simple scenerio, a simple code review may have discovered the error. But as the code expands, or other clients use the library, the error may not be so easily detected.

The question is, is it possible to design a better library to help the client discover these errors at run-time?

The answer is Yes.

Enum Polymorphism

If enum polymorphism was available, this would be simply solved by:

public enum AddressRead : byte
{
    // A read-only serial number
    SerialNumber = 0x01,
}

public enum AddressWrite : AddressRead
{
    // A user-defined name (read and write).
    Name = 0x02
}

public static string Read(AddressRead address);
public static void Write(AddressWrite address, string value);

This library now ensures that only addresses that are AddressWrite can be written to; and both the AddressRead and AddressWrite can be read.

Currently, the C-Sharp language does not support this concept of enum polymorphism… but let’s not let that stop us.

This code can be easily changed to work within the constraints of the C-Sharp language, and provide the compile-time support we endevour to seek.

Enum Polymorphism – written with classes

Below is the new library written using classes to define the types of available addresses; and a static class to hold all the enumerations.

public static class Address
{
    public static readonly AddressRead SerialNumber = new AddressRead(0x01);
    public static readonly AddressReadAndWrite Name = new AddressReadAndWrite(0x02);
}

public class AddressRead : byte
{
    public AddressRead(byte value)
    {
        this.Value = value;
    }
    public byte Value { get; private set; }
}

public class AddressReadAndWrite : AddressRead
{
    public AddressReadAndWrite(byte value)
        : base(value)
    {
    }
}

public static string Read(AddressRead address);
public static void Write(AddressReadAndWrite address, string value);

Now, for the given client code …

static void Main(string[] args)
{
    // All good, can read from both addresses
    string serial_number = Read(Address.SerialNumber);
    string name = Read(Address.Name);

    // Good - can write to name address
    Write(Address.Name, "john smith");

    // COMPILE-TIME ERROR !
    // Argument 1: cannot convert from 'Program.AddressRead'
    // to 'Program.AddressReadAndWrite'
    Write(Address.SerialNumber, "0123456789");
}

The code will now correctly fail at compile time.

Mutable & Immutable Classes

This post describes a method for creating a mutable and immutable version of the same class. At a first glance, there may seem to be two obvious ways to approach this problem; either:

  1. Create a base Mutable Class and derive a Immutable version of this class; or
  2. Create a base Immutable Class and derive a Mutable version of this class.

To help decide which may be the best design, we can refer back to the principles of object-oriented design. The Liskov Substitution Principle states that subtypes must be substitutable for their base types.

Don’t inherit unless you really are (and can be treated in all respects) like what you derive from.

Keeping this in mind, we can see that solution one would violate this principle. You can not treat an Immutable object as a Mutable object. i.e. you would not want a user to be able to cast an immutable object, to it’s parents-type, so that the user could then mutate that object.

A simple implementation of solution two is shown below.

    public class ImmutableClass
    {
        public int SomeProperty { get; protected set; }
        public string AnotherProperty { get; protected set; }
    }

    public class MutableClass : ImmutableClass
    {
        public new int SomeProperty
        {
            get
            {
                return base.SomeProperty;
            }
            set
            {
                base.SomeProperty = value;
            }
        }

        public new string AnotherProperty
        {
            get
            {
                return base.AnotherProperty;
            }
            set
            {
                base.AnotherProperty = value;
            }
        }
    }