Which model should be used to express Hierarchical enumeration?

I am experimenting with an API to publish values ​​at a given time (value and time tuples). These samples will be used by a data viewer (such as a graph).

I want to associate a value with a quantity and a unit, for example, a length in meters. Thus, my “viewer" can scale it accordingly.

I am looking for a kind of hierarchical enumeration, for example:

enum Quantity { Mass.Kg, Mass.g, Length.m, Length.mm } 

But this does not exist in C #.

I am not sure the best pattern to express this, and I came up with the following. Is there a recognized 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); } } } 
+7
source share
5 answers

I think it's better to create the Unit class for the unit of measure and the Quantity class, which associates the unit of measure with the sum. Take a look at the Quantitative Template for this idea. Since you also want to write the "type" of the unit of measure, you can create a UnitType class that records this 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 must make them the correct value types by overriding Equals and GetHashCode )

The Unit class can be extended to provide, for example, conversions, compound units, formatting, and parsing.

Then you can define common cases inside 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 will also be an immutable value type (just showing its use):

 var eniacWeight = new Quantity(27, Mass.Tons); 

Or you can use extension methods to create Quantity s:

 var eniacWeight = 27.Tons(); 

(from ENIAC )

+2
source

Your proposed approach seems reasonable to me, and I use something similar in my project. However, I preserve the actual value of the part of the object, and I use struct instead of class , since they are natural value types. Inheritance is not necessary here (and this is not possible with structs, anyways), so I use the interface to create a contract and, if necessary, fulfill the restrictions (I called it IUnitOfMeasure ).

I do not recommend creating one enumeration with all units of measure of various types; this is hell checking block to make sure someone didn't refer to the mass device when working with length.

 public interface IUnitOfMeasure<TThis> where TThis : IUnitOfMeasure<TThis> { TThis ConvertTo(TThis value); } public struct Mass : IUnitOfMeasure<Mass> { public enum Units { Gram, Kilogram } private double _value; private Mass.Units _unit; public double Value { get { return _value; } } public Mass.Units Unit { get { return _unit; } } public Mass(double value, Mass.Units unit) { _value = value; _unit = unit; } public Mass ConvertTo(Mass value) { switch(value.Unit) { case Units.Gram: return new Mass(Unit == Units.Gram ? Value : Value/1000, Units.Gram); case Units.Kilogram: return new Mass(Unit == Units.Gram ? Value*1000 : Value, Units.Kilogram); default: throw new NotImplementedException(); } } public override string ToString() { return string.Format("{0} {1}", Value, Unit); } public static readonly Mass G = new Mass(0, Units.Gram); public static readonly Mass Kg = new Mass(0, Units.Kilogram); } 

Using:

 var kg = new Mass(5.0, Mass.Units.Kilogram); Console.WriteLine(kg); // writes "5 Kilogram" var g = kg.ConvertTo(Mass.G); Console.WriteLine(g); // writes ".005 Gram" 

If you don't care about storing the value and just want to keep the enum / static values ​​in a central place:

 public static class UnitOfMeasure { public enum Mass { Gram, Kilogram } public enum Length { Meter, Kilometer } // etc. } 

Usage: var unit = UnitOfMeasure.Mass.Kilogram;

+1
source

It's impossible. Enums are primitive types and cannot inherit from other enumerations, since inheritance is a property of objects.

+1
source

Hierarchical enumeration is not possible, as indicated above. If you use metrics exclusively, you can use standard prefixes if that helps.

 enum MeasurementUnits { Gram, Metre, Litre, Hectare // etc } enum MeasurementPrefix { Milli, Natural, Kilo, Mega // etc } 

This may not be exactly what you want, but it will provide the type of “grouping” you can look for (for example, group measurements regarding length, weight, etc., by checking their “units” value).

+1
source

You cannot enter inheritance with enumerations. Enums is just a convenience mechanism that allows you to use meaningful text identifiers in your code. From the code you have, I suggest you use an enumeration like:

 public enum UnitOfMeasure { MassGrams, MassKg, LengthMM, LengthCM, . . . } 

Or separate it where necessary, so that the mass and length are determined separately, for example.

Inheritance is what you have introduced into your thinking about this problem, but it is not necessary for your solution. When you want to deal with Mass, you only look at flags / enums corresponding to mass.

0
source

All Articles