Physical Quantities in C#

Scientific applications usually perform lots of calculations with physical quantities. If you do not represent them properly in your code you run the risk of mixing them up. For example, it’s easy to add a value in meters to a value in kilometers if you simply work with variables of primitive types like double or decimal. It can help to encode the unit in the variable name, e.g. massInKilogram, but it’s much better to let the type system handle it.

So here’s some C# code which models a generic physical quantity and its unit:

public abstract class Quantity<T> where T : Quantity<T>, new()
{
    private decimal value;

    public decimal In(Unit<T> unit)
    {
        return value / unit.Factor;
    }

    public string ToStringIn(Unit<T> unit)
    {
        return string.Format("{0} {1}", In(unit), unit.Text);
    }

    public class Unit<Q> where Q : Quantity<Q>, new()
    {
        public decimal Factor { get; private set; }
        public string Text { get; private set; }

        public Unit(string representation, decimal factor)
        {
            Text = representation;
            Factor = factor;
        }

        public static Q operator *(decimal value, Unit<Q> unit)
        {
            var quantity = new Q();
            quantity.value = value * unit.Factor;
            return quantity;
        }
    }
}

With this base class we can easily implement some quantities:

class Duration : Quantity<Duration>
{
    public static readonly Unit<Duration> Millisecond = new Unit<Duration>("ms", 1e-3M);
    public static readonly Unit<Duration> Second = new Unit<Duration>("s", 1M);
    public static readonly Unit<Duration> Minute = new Unit<Duration>("min", 60M);
}

class Mass : Quantity<Mass>
{
    public static readonly Unit<Mass> Milligram = new Unit<Mass>("mg", 1e-3M);
    public static readonly Unit<Mass> Gram = new Unit<Mass>("g", 1M);
    public static readonly Unit<Mass> Kilogram = new Unit<Mass>("kg", 1e+3M);
}

And this is how they are used:

var t = 30 * Duration.Second;
Console.WriteLine(t.ToStringIn(Duration.Minute));
Console.WriteLine(t.ToStringIn(Duration.Second));
Console.WriteLine(t.ToStringIn(Duration.Millisecond));

var m = 10 * Mass.Gram;
Console.WriteLine(m.ToStringIn(Mass.Milligram));
Console.WriteLine(m.ToStringIn(Mass.Gram));
Console.WriteLine(m.ToStringIn(Mass.Kilogram));

The Unit class uses operator overloading to overload the multiplication operator. Instead of calling a constructor directly we use multiplication with a unit to create new instances of a quantity.

The type system prevents nonsense like this:

// does not compile
(10 * Mass.Gram).In(Duration.Minute);

You will probably want to overload more operators of the quantity classes depending on your use case. You can also overload operators to produce instances of new quantities:

Velocity v = (3.5 * Length.Kilometer) / (10 * Duration.Minute);

class Velocity : Quantity
{
    public static readonly Unit MeterPerSecond = new Unit("m/s", 1M);
}

class Length : Quantity
{
    public static readonly Unit Meter = new Unit("m", 1M);
    public static readonly Unit Kilometer = new Unit("m", 1e+3M);

    public static Velocity operator /(Length s, Duration t)
    {
        return (s.In(Length.Meter) / t.In(Duration.Second)) * Velocity.MeterPerSecond;
    }
}

If you do not want to hand craft your quantities you might want to check out existing libraries for working with quantities like QuantityTypes.

Declaration-site and use-site variance explained

A common question posed by programming novices who have their first encounters with parametrized types (“generics” in Java and C#) is “Why can’t I use a List<Apple> as a List<Fruit>?” (given that Apple is a subclass of Fruit) Their reasoning usually goes like this: “An apple is a fruit, so a basket of apples is a fruit basket, right?”

Here’s another, similar, example:

Milk is a dairy product, but is a bottle of milk a dairy product bottle? Try putting a Cheddar cheese wheel into the milk bottle (without melting or shredding the cheese!). It’s obviously not that simple.

Let’s assume for a moment that it was possible to use a List<Apple> as a List<Fruit>. Then the following code would be legal, given that Orange is a subclass of Fruit as well:

List<Apple> apples = new ArrayList<>();
List<Fruit> fruits = apples;
fruits.add(new Orange());

// what's an orange doing here?!
Apple apple = apples.get(0);

This short code example demonstrates why it doesn’t make sense to treat a List<Apple> as a List<Fruit>. That’s why generic types in Java and C# don’t allow this kind of assignment by default. This behaviour is called invariance.

Variance of generic types

There are, however, other cases of generic types where assignments like this actually could make sense. For example, using an Iterable<Apple> as an Iterable<Fruit> is a reasonable wish. The opposite direction within the inheritance hierarchy of the type parameter is thinkable as well, e.g. using a Comparable<Fruit> as a Comparable<Apple>.

So what’s the difference between these generic types: List<T>, Iterable<T>, Comparable<T>? The difference is the “flow” direction of objects of type T in their interface:

  1. If a generic interface has only methods that return objects of type T, but don’t consume objects of type T, then assignment from a variable of Type<B> to a variable of Type<A> can make sense. This is called covariance. Examples are: Iterable<T>, Iterator<T>, Supplier<T>inheritance
  2. If a generic interface has only methods that consume objects of type T, but don’t return objects of type T, then assignment from a variable of Type<A> to a variable of Type<B> can make sense. This is called contravariance. Examples are: Comparable<T>, Consumer<T>
  3. If a generic interface has both methods that return and methods that consume objects of type T then it should be invariant. Examples are: List<T>, Set<T>

As mentioned before, neither Java nor C# allow covariance or contravariance for generic types by default. They’re invariant by default. But there are ways and means in both languages to achieve co- and contravariance.

Declaration-site variance

In C# you can use the in and out keywords on a type parameter to indicate variance:

interface IProducer<out T> // Covariant
{
    T produce();
}

interface IConsumer<in T> // Contravariant
{
    void consume(T t);
}

IProducer<B> producerOfB = /*...*/;
IProducer<A> producerOfA = producerOfB;  // now legal
// producerOfB = producerOfA;  // still illegal

IConsumer<A> consumerOfA = /*...*/;
IConsumer<B> consumerOfB = consumerOfA;  // now legal
// consumerOfA = consumerOfB;  // still illegal

This annotation style is called declaration-site variance, because the type parameter is annotated where the generic type is declared.

Use-site variance

In Java you can express co- and contravariance with wildcards like <? extends A> and <? super B>.

Producer<B> producerOfB = /*...*/;
Producer<? extends A> producerOfA = producerOfB; // legal
A a = producerOfA.produce();
// producerOfB = producerOfA; // still illegal

Consumer<A> consumerOfA = /*...*/;
Consumer<? super B> consumerOfB = consumerOfA; // legal
consumerOfB.consume(new B());
// consumerOfA = consumerOfB; // still illegal

This is called use-site variance, because the annotation is not placed where the type is declared, but where the type is used.

Arrays

The variance behaviour of Java and C# arrays is different from the variance behaviour of their generics. Arrays are covariant, not invariant, even though a T[] has the same problem as a List<T> would have if it was covariant:

Apple[] apples = new Apple[10];
Fruit[] fruits = apples;
fruits[0] = new Orange();
Apple apple = apples[0];

Unfortunately, this code compiles in both languages. However, it throws an exception at runtime (ArrayStoreException or ArrayTypeMismatchException, respectively) in line 3.