2

I want to come up with a way to make it easy to write classes that represent quantities with units, such as length, weight, etc. For example,

var height = new Length(32.2, LengthUnit.M);
var weight = new Weight(164.2, WeightUnit.Lb);

In addition, these classes should automatically handle unit conversions and implement common mathematical operations. For example,

var length1 = new Length(3.36, LengthUnit.M);
var length2 = new Length(34.5, LengthUnit.Cm);
var totalLength = length1 + length2;

My initial solution was to create a base class Quantity<TUnit>, where TUnit is a "unit" type (for example, LengthUnit) that defines scale conversions. But I'm running into the issue that my operator overloading methods can't possibly know the derived type to return. This means that in the example above, totalLength is of type Quantity<LengthUnit>, not Length. If C# had a way to create an alias for a class, so that Length was equivalent to Quantity<LengthUnit>, things would work, but C# lacks such a feature!

I could pass on the work of defining all the operator overloading methods to the derived classes, but this wouldn't make it an "easy" way to create new quantity types.

Any better ideas?

Update: Here's an implementation of my initial solution.

public abstract class Unit
{
    protected Unit(string symbol, double factor)
    {
        Symbol = symbol;
        Factor = factor;
    }

    public string Symbol { get; }
    public double Factor { get; }
}

public class Quantity<TUnit> where TUnit : Unit
{
    private readonly double baseValue;

    protected Quantity(double value, TUnit unit)
    {
        baseValue = value * unit.Factor;
    }

    private Quantity(double value)
    {
        baseValue = value;
    }

    public static Quantity<TUnit> operator +(Quantity<TUnit> a, Quantity<TUnit> b)
    {
        return new Quantity<TUnit>(a.baseValue + b.baseValue);
    }
}

public class LengthUnit : Unit
{
    public static readonly LengthUnit M = new LengthUnit("m", 1);
    public static readonly LengthUnit Cm = new LengthUnit("cm", 0.01);

    protected LengthUnit(string symbol, double factor)
        : base(symbol, factor) { }
}

public class Length : Quantity<LengthUnit>
{
    public Length(double value, LengthUnit unit)
        : base(value, unit) { }
}
10
  • 4
    What is the fundamental difference between Length and Quantity<LengthUnit>? Other than that one has a more recognizable name to read in the source code. Jul 13 at 11:29
  • "C# lacks such a feature"? what about using Length = Quantity<LengthUnit>?
    – Caleth
    Jul 13 at 11:48
  • E.g. dotnetfiddle.net/Xt4SUw
    – Caleth
    Jul 13 at 12:06
  • @Caleth Thanks, but I guess I meant in a way that doesn't require specifying that statement in every file that uses it, and instead just once at a namespace level.
    – redcurry
    Jul 13 at 13:37
  • 3
    If you are using C#, then F# might be worth a look. They have "Units of Measure" as a language feature, which could solve your problems potentially if the F#-C# interop works for your purposes: docs.microsoft.com/en-us/dotnet/fsharp/language-reference/…
    – Graham
    Jul 13 at 14:36

2 Answers 2

1

To try to answer the question directly, here is my not-so-pretty implementation

For base class, we need to force TSelf into type parameter, so we can make a generic operator overloading on it.

public abstract class Quantity<TSelf, TUnit> 
    where TUnit : Unit
    where TSelf : Quantity<TSelf, TUnit>
{
    protected readonly double baseValue;

    protected Quantity(double value, TUnit unit)
    {
        baseValue = value * unit.Factor;
    }

    private Quantity(double value)
    {
        baseValue = value;
    }

    public abstract TSelf WithValue(double value);        

    public static TSelf operator +(TSelf a, Quantity<TSelf, TUnit> b)
    {
        return a.WithValue(a.baseValue + b.baseValue);
    }
}

Notice the WithValue method. This exists so we can create a quantity in the same unit but with different value.

For the Length class, we need to implement WithValue manually, yet it is trivial:

public class Length : Quantity<Length, LengthUnit>
{    
    public Length(double value, LengthUnit unit)
        : base(value, unit) { }

    public override Length WithValue(double value) => new Length(value, LengthUnit.M);
}

While the declaration is not so intuitive, e.g. it is strange to declare Length as Quantity<Length, LengthUnit> and WithValue method seems out of places, I think it solves the OP question to make it easy to create custom quantity type.

4
  • Great solution! I had given up and started using T4 to generate the code for each quantity type, but this is better (even if a bit awkward). Thanks.
    – redcurry
    Jul 20 at 14:45
  • In Quantity, is there any difference between TSelf and Quantity<TSelf, TUnit>, i.e. couldn't it be TSelf operator+(TSelf, TSelf)?
    – Caleth
    Jul 21 at 8:24
  • @Caleth It could be TSelf, TSelf. The minimum requirement for C# operator overloading is that at least one of them must be TSelf, so I put it that way.
    – tia
    Jul 21 at 9:39
  • @Caleth I was wrong. It couldn't be TSelf, TSelf because at least one of them must be Quantity<TSelf, TUnit>
    – tia
    Jul 21 at 16:24
9

Representing units in type systems remains an open problem in programming language design. Interesting approaches (e.g. as available in F#) are not available in C#.

We have the following basic possibilities for representing quantities:

  • primitive values, e.g. 12.3
  • units as types, e.g. new Cm(12.3)
  • dimensions as types, e.g. new Length(12.3, LengthUnit.Cm)

Primitive values are not really an option because they will lead to type confusion, which is what lead to the loss of the Mars Climate Orbiter mission.

Representing units as types seems natural, but will get complicated really fast as soon as these types are involved in some algebra. For example, you can't easily add two length values a + b unless you implement appropriate operators like operator +(In a, Cm b) for any allowed combination of units, or if you implement possibility #3 anyway.

So this leaves us with the solution where the types describe dimensions (e.g. length, area, volume, temperature, pressure, money) with no static information about units (e.g. meters, hectars, gallons, degrees Celsius, bar, Swedish krona).

There is a huge disadvantage here that we can't easily describe composite dimensions/units like M L²/T/eV s, the type of the Planck constant. We'd have to create a custom named type for that dimension. This is a problem when designing a computer algebra system or when writing physics programs, but that isn't a problem for most business-oriented systems where we merely want to model types in order to avoid conversion errors.

How would we implement a dimension type? It is generally a good idea to pick an unambiguous internal representation, and then convert at the boundaries. The original unit is erased, which limits the number of types we have to deal with. For example, we might write:

enum LengthUnit {
  METER,
  CM,
  INCH,
  FEET,
}

double LengthUnitFactor(LengthUnit u) { ... }

struct Length {
  private double meters;

  public Length(double raw, LengthUnit unit) {
    meters = raw / LengthUnitFactor(unit);
  }

  public double AsUnit(LengthUnit unit) => meters * LengthUnitFactor(unit);

  public static Length operator +(Length a, Length b) =>
    new Length(a.meters + b.meters);
}

var height = (new Length(5, LengthUnit.FEET) + new Length(7, LengthUnit.INCH)).AsUnit(LengthUnit.METER);

However, this is cumbersome to use.

Libraries like NodaTime that use this approach for representing time durations provide extra methods that allow us to easily construct dimensions and retrieve them in any unit.

Here, we might write:

readonly struct Length {
  private Length(double meters) { AsMeters = meters }

  public static Length operator +(Length a, Length b) =>
    new Length(a.AsMeters + b.AsMeters);

  public double AsMeters { get; init; }
  public static Length FromMeter(double meters) => new Length(meters);

  public double AsCm => AsMeters * 100;
  public static Length FromCm(double cm) => new Length(cm / 100);

  private static double FEET_IN_M = 0.3048;
  public double AsFeet => AsMeters / FEET_IN_M;
  public static Length FromFeet(double feet) => new Length(feet * FEET_IN_M);

  public double AsInch => AsFeet * 12;
  public static Length FromInch(double inch) => Length.FromFeet(inch / 12);
}

var height = (Length.FromFeet(5) + Length.FromInch(7)).AsMeters;

What this API style does provide is very good usability in most scenarios. It makes it difficult to accidentally confuse units. It allows us to perform some reasonable math operations with little difficulty.

This implementation is not extensible, but most programs really do not need that kind of extensibility where outside consumers of an API could define their own units. Some degree of extensibility is possible though via extension methods, or by simply defining free functions, e.g. LengthFromParsec(1.3), AsParsecs(length).

Note that currency is fundamentally different from physical units. Physical units have defined and fixed relationships. In contrast, currency exchange rates float, and performing an exchange has fees. For a program that deals with multiple currencies, how to treat them is a business problem so that appropriate requirements have to be elicited first. Also, doubles are appropriate for physical measurements but never for monetary values. In a lot of cases, a Money(int amount, string currency) class would be most suitable. Math on currency values would then only be possible with run-time checks that both money objects use the same currency.

1
  • "units as types" is much nicer in C++, where values can be a template parameter, so point 2 and point 3 can be the same thing
    – Caleth
    Jul 19 at 13:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.