Stay safe with your units! Advanced units of measure in .NET.

WhiteBlackGoose
ITNEXT
Published in
5 min readDec 22, 2021

--

This mini-article shows a concept of advanced units of measure, fully type safe, with automatic unit conversion, without runtime dispatch, powered by generic math! (for the impatient: github)

Example of work

Probably one of the most if not the most popular library for units for .NET is UnitsNet. It provides a lot of API for many many cases and probably is great (haven’t tried myself). There’s also F#’s Units of Measure. It doesn’t provide additional API or type conversions, however it provides compile time safety (for F# consumers) and is overheadless.

So why not to try to make our own interopable (available equally for F# and C# devs) units of measure system? A one so that anyone could add their own units! A one without much of runtime overhead. At the same time fully compile time safe! Also with arbitrary types (instead of doubles/floats). And finally, with type conversions.

Making the reality

The idea here is that we don’t really care about “dimensions”, be that length, mass, whatever. We simply forget those exist. However, what we do keep in mind is that each unit has its base unit.

What is base unit? Theoretically it can be any unit. For simplicity I will make SI’s units as base. For example, for kilometer the base unit is meter (there are 1000 meters in 1 kilometer). Mile’s base unit is meter too (about 1600 meters). Finally, meter’s base unit is itself (there’s 1 meter in 1 meter).

Our Base Unit interface

Base property provides the amount of your base unit in your unit. For example, here’s what Minute looks like, whose base type is second:

Minute implementing IBaseUnit interface

TNumber is there because we’re doing generic math, so it can be any entity (which corresponds to some constraints). Number60 is a bit ugly, but you cannot get 60 out of a generic type.

Now, what about arithmetic operations? For division, for instance, we create a unit too. Remember when I said every unit has its base unit? So does division.

Implementation of Div unit

It looks a bit verbose because of a whole “mess” with generic type constraints. But the key here is that it also has base unit. Which is division of bases of its numerator and denominator. For instance, km/min would have a base unit m/s. This means that we can easily convert our types, and all we need is to check if they have the same base type. Let’s look at the conversion we can have:

Implementation of conversion of units

Since it’s all generic constraints, it will be impossible to convert imcompatible units, but very easy to convert those compatible:

Converting meters to seconds gives a compilation error

Similarly we implement compile time safe Add. Note, that we don’t have generic operators, so implementing + won’t be possible for our case.

Implementation of Add method

It looks a bit massive. But the key here is that we have different units, but the same base unit. For example, you can add seconds and minutes (20s + 1min = 80s). You can add kilometers and miles (1 km + 1 mile = 2.6 km). But you cannot add seconds and meters. So the added units must share the same base unit.

In the end, let me demonstrate a few results.

Demonstration of the work

Demonstration of work

F# has generic operators. I couldn’t get them the way I want, but for a proof of concept, we still have something:

F#’s PoV on my UoMs

Remember, it’s generic math. That means we can literally do any type which implements a few interfaces. For example, we can try using AngouriMath.Experimental package, which is a version of AngouriMath which implements generic math interfaces.

Example of symbolic algebra as used as units’ value

Efficiency

What about efficiency? It’s not too bad, since you don’t need to dispatch anything in runtime. So it’s just one floating addition if the units are the same, but a few more operations if the units are not the same. More info.

Conclusion

In the end, it may be better or worse than the units you’re already using (if any). See the comparing table (🥇 means best, 🥉 means worst, 🥈 is somewhere in the middle):

Comparing different units of measure

*Ext. is extendability (able to extend the built-in things). Extendability of units means you can add more units. Extendability of dimensions means you can add “dimensions” (e. g. length, mass, etc.).

Since the table may be changed, see the relevant value here.

Github of the conceptual repo. My github, twitter.

Thanks for your attention!

--

--