What pattern should I use to express a Hierarchical Enum?
architecture, c#, design-patterns, enums
Solution
I think it's better to create a `Unit` class for the unit of measure and a `Quantity` class that associates a unit of measure with an amount. Look at the Quantity pattern for the idea. Since you also want to record the "type" of the unit of measure, you could create a `UnitType` class that records that information:
public sealed partial class UnitType {
public string Name { get; private set; }
public UnitType(string name) {
Name = name;
}
}
public sealed partial class Unit {
public string Name { get; private set; }
public UnitType Type { get; private set; }
public Unit(string name, UnitType type) {
Name = name;
Type = type;
}
}
(You should make them proper value types by overriding `Equals` and `GetHashCode`)
The `Unit` class can be extended to provide for e.g. conversions, compound units, formatting and parsing.
Then, you can define the common cases inside the classes:
public partial class UnitType {
public static readonly UnitType Mass = new UnitType("Mass");
public static readonly UnitType Length = new UnitType("Length");
}
public partial class Unit {
public static readonly Unit Grams = new Unit("g", UnitType.Mass);
public static readonly Unit Kilos = new Unit("kg", UnitType.Mass);
// ...
}
Or define your "hierarchies" with static classes:
public static class Mass {
public static readonly UnitType Type = new UnitType("Mass");
public static readonly Unit Grams = new Unit("g", Type);
public static readonly Unit Kilos = new Unit("kg", Type);
...
}
public static class Length ...
The `Quantity` class would also be an immutable value type (just showing its usage):
var eniacWeight = new Quantity(27, Mass.Tons);
Or you could use extension methods to create `Quantity`s:
var eniacWeight = 27.Tons();
(from ENIAC)
Problem
I'm experimenting with an API for publishing values at a given time (tuples of value and time). These samples will be used by a data viewer (e.g. a graph). I want to associate the value with a Quantity and a Unit, for example length in meters. That way my "viewer" can scale it appropriately. I'm looking for a sort of hierarchical enum, like this: ``` enum Quantity { Mass.Kg, Mass.g, Length.m, Length.mm } ``` But this doesn't exist in C#. I'm not sure the best pattern to express this and I've come up with the following. Is there a recognised, or better way to do this? ``` using System; using Moq; namespace ConsoleApplication26 { class Program { static void Main(string[] args) { //use a Mock to play with the API Mock<ITelemetryPublisherFactory> mockTelemetryPublisherFactory = new Mock<ITelemetryPublisherFactory>(); var telemetryPublisherFactory = mockTelemetryPublisherFactory.Object; //example usages var massTelemetryPublisher = telemetryPublisherFactory.GetChannelSamplePublisher<Double>("My Mass", Mass.Kg); massTelemetryPublisher.PublishChannelSampleAtTimeNow(83.4); var lengthTelemetryPublisher = telemetryPublisherFactory.GetChannelSamplePublisher<Int32>("My Height", Length.μm); lengthTelemetryPublisher.PublishChannelSampleAtTimeNow(1800000); //10 years time.. lengthTelemetryPublisher.PublishChannelSampleAtTimeNow(1800000); massTelemetryPublisher.PublishChannelSampleAtTimeNow(120.1); } } public interface ITelemetryPublisherFactory { ITelemetryPublisher<T> GetChannelSamplePublisher<T>(String channelName, Quantity quantity); } public interface ITelemetryPublisher<T> { void PublishChannelSampleAtTimeNow(T sampleValue); } public abstract class Quantity {} public class Mass : Quantity { private enum Unit { g, Kg } private readonly Unit _unit; private Mass(Unit unit) { _unit = unit; } public static Quantity Kg {get { return new Mass(Unit.Kg); }} public static Quantity g { get { return new Mass(Unit.g); } } public override string ToString() { return String.Format("Mass.{0}", _unit); } } public class Length : Quantity { private enum Unit { m, mm, μm, beardSecond } private readonly Unit _unit; private Length(Unit unit) { _unit = unit; } public static Quantity m { get { return new Length(Unit.m); } } public static Quantity mm { get { return new Length(Unit.mm); } } public static Quantity μm { get { return new Length(Unit.μm); } } public static Quantity beardSecond { get { return new Length(Unit.beardSecond); } } public override string ToString() { return String.Format("Length.{0}", _unit); } } } ```