Units of measure in C# - almost
Asked Answered
B

14

65

Inspired by Units of Measure in F#, and despite asserting (here) that you couldn't do it in C#, I had an idea the other day which I've been playing around with.

namespace UnitsOfMeasure
{
    public interface IUnit { }
    public static class Length
    {
        public interface ILength : IUnit { }
        public class m : ILength { }
        public class mm : ILength { }
        public class ft : ILength { }
    }
    public class Mass
    {
        public interface IMass : IUnit { }
        public class kg : IMass { }
        public class g : IMass { }
        public class lb : IMass { }
    }

    public class UnitDouble<T> where T : IUnit
    {
        public readonly double Value;
        public UnitDouble(double value)
        {
            Value = value;
        }
        public static UnitDouble<T> operator +(UnitDouble<T> first, UnitDouble<T> second)
        {
            return new UnitDouble<T>(first.Value + second.Value);
        }
        //TODO: minus operator/equality
    }
}

Example usage:

var a = new UnitDouble<Length.m>(3.1);
var b = new UnitDouble<Length.m>(4.9);
var d = new UnitDouble<Mass.kg>(3.4);
Console.WriteLine((a + b).Value);
//Console.WriteLine((a + c).Value); <-- Compiler says no

The next step is trying to implement conversions (snippet):

public interface IUnit { double toBase { get; } }
public static class Length
{
    public interface ILength : IUnit { }
    public class m : ILength { public double toBase { get { return 1.0;} } }
    public class mm : ILength { public double toBase { get { return 1000.0; } } }
    public class ft : ILength { public double toBase { get { return 0.3048; } } }
    public static UnitDouble<R> Convert<T, R>(UnitDouble<T> input) where T : ILength, new() where R : ILength, new()
    {
        double mult = (new T() as IUnit).toBase;
        double div = (new R() as IUnit).toBase;
        return new UnitDouble<R>(input.Value * mult / div);
    }
}

(I would have liked to avoid instantiating objects by using static, but as we all know you can't declare a static method in an interface) You can then do this:

var e = Length.Convert<Length.mm, Length.m>(c);
var f = Length.Convert<Length.mm, Mass.kg>(d); <-- but not this

Obviously, there is a gaping hole in this, compared to F# Units of measure (I'll let you work it out).

Oh, the question is: what do you think of this? Is it worth using? Has someone else already done better?

UPDATE for people interested in this subject area, here is a link to a paper from 1997 discussing a different kind of solution (not specifically for C#)

Boot answered 8/12, 2008 at 7:32 Comment(8)
Take a look at Frink calculator and Frink programming language.Jojo
I wonder if anyone has approached units in C# with attributes for class property values.Longs
Frink is the bomb for this type of problem.Quadragesimal
I may be missing something obvious here, but why would you want/need another (i.e. other than F#) CLR-based implementation of Units of Measure? Or is this just for "the sake of doing it"?Ursuline
@pblasucci, just for the sake of it. (Just to be picky, the F# implementation isn't CLR-based, it all happens in the compiler, the runtime never sees anything)...Boot
@Benjol: Sure, fair point about the compiler. I should have said 'CLR-usable'. Cheers!Ursuline
@ja72: I use attributes to declare the units but this is purely for display purposes. I use reflection to find out what the units actually are so that I can convert to whatever the UI wants. It would be very nice to be able to use these annotations to catch attempts to add apples and oranges.Picco
There is a feature request for this for C#. For now I think this project, QuantityTypes, can do the job.Huang
E
44

You are missing dimensional analysis. For example (from the answer you linked to), in F# you can do this:

let g = 9.8<m/s^2>

and it will generate a new unit of acceleration, derived from meters and seconds (you can actually do the same thing in C++ using templates).

In C#, it is possible to do dimensional analysis at runtime, but it adds overhead and doesn't give you the benefit of compile-time checking. As far as I know there's no way to do full compile-time units in C#.

Whether it's worth doing depends on the application of course, but for many scientific applications, it's definitely a good idea. I don't know of any existing libraries for .NET, but they probably exist.

If you are interested in how to do it at runtime, the idea is that each value has a scalar value and integers representing the power of each basic unit.

class Unit
{
    double scalar;
    int kg;
    int m;
    int s;
    // ... for each basic unit

    public Unit(double scalar, int kg, int m, int s)
    {
       this.scalar = scalar;
       this.kg = kg;
       this.m = m;
       this.s = s;
       ...
    }

    // For addition/subtraction, exponents must match
    public static Unit operator +(Unit first, Unit second)
    {
        if (UnitsAreCompatible(first, second))
        {
            return new Unit(
                first.scalar + second.scalar,
                first.kg,
                first.m,
                first.s,
                ...
            );
        }
        else
        {
            throw new Exception("Units must match for addition");
        }
    }

    // For multiplication/division, add/subtract the exponents
    public static Unit operator *(Unit first, Unit second)
    {
        return new Unit(
            first.scalar * second.scalar,
            first.kg + second.kg,
            first.m + second.m,
            first.s + second.s,
            ...
        );
    }

    public static bool UnitsAreCompatible(Unit first, Unit second)
    {
        return
            first.kg == second.kg &&
            first.m == second.m &&
            first.s == second.s
            ...;
    }
}

If you don't allow the user to change the value of the units (a good idea anyways), you could add subclasses for common units:

class Speed : Unit
{
    public Speed(double x) : base(x, 0, 1, -1, ...); // m/s => m^1 * s^-1
    {
    }
}

class Acceleration : Unit
{
    public Acceleration(double x) : base(x, 0, 1, -2, ...); // m/s^2 => m^1 * s^-2
    {
    }
}

You could also define more specific operators on the derived types to avoid checking for compatible units on common types.

Edee answered 8/12, 2008 at 8:3 Comment(3)
Yeh, I knew that was what was missing, I quite like your solution, but as you say, it's not compile time. Vote up anyways.Boot
I don't love seeing initializers which grow in complexity when we add more basic units. Since you are already losing the ability to detect wrong units at compile time, you could a step further and just use a dictionary mapping a string or enumeration to int rather than having a separate field for each type.Kuibyshev
There are only 7 base units if you take the SI system (time, mass, length, temperature, luminous intensity, substance amount and electrical current). If you add a multiplier value to Unit which is the conversion factory back to the SI representation you can get a fairly good model.Crib
R
20

Using separate classes for different units of the same measure (e.g., cm, mm, and ft for Length) seems kind of weird. Based on the .NET Framework's DateTime and TimeSpan classes, I would expect something like this:

Length  length       = Length.FromMillimeters(n1);
decimal lengthInFeet = length.Feet;
Length  length2      = length.AddFeet(n2);
Length  length3      = length + Length.FromMeters(n3);
Rosetta answered 8/12, 2008 at 7:46 Comment(1)
This was my first instinct too. The downside with this is that you have to explicitely define operators that combine all permutations of units. This gets much more complicated when you start combining different units together like Velocity (Length / TimeSpan) where you hyave a very large number of FromXXX conversions you would need to support.Mia
M
20

You could add extension methods on numeric types to generate measures. It'd feel a bit DSL-like:

var mass = 1.Kilogram();
var length = (1.2).Kilometres();

It's not really .NET convention and might not be the most discoverable feature, so perhaps you'd add them in a devoted namespace for people who like them, as well as offering more conventional construction methods.

Meingolda answered 1/6, 2010 at 12:43 Comment(0)
N
18

I recently released Units.NET on GitHub and on NuGet.

It gives you all the common units and conversions. It is light-weight, unit tested and supports PCL.

Example conversions:

Length meter = Length.FromMeters(1);
double cm = meter.Centimeters; // 100
double yards = meter.Yards; // 1.09361
double feet = meter.Feet; // 3.28084
double inches = meter.Inches; // 39.3701
Nuncio answered 21/7, 2013 at 22:55 Comment(0)
D
12

Now such a C# library exists: http://www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

It has almost the same features as F#'s unit compile time validation, but for C#. The core is a MSBuild task, which parses the code and looking for validations.

The unit information are stored in comments and attributes.

Drouin answered 3/7, 2012 at 15:5 Comment(1)
interesting effort, but the author admits he is dissatisfied with the project and suggests starting again from scratch. A similar library can be found here: github.com/InitialForce/UnitsNetGerund
S
4

Here's my concern with creating units in C#/VB. Please correct me if you think I'm wrong. Most implementations I've read about seem to involve creating a structure that pieces together a value (int or double) with a unit. Then you try to define basic functions (+-*/,etc) for these structures that take into account unit conversions and consistency.

I find the idea very attractive, but every time I balk at what a huge step for a project this appears to be. It looks like an all-or-nothing deal. You probably wouldn't just change a few numbers into units; the whole point is that all data inside a project is appropriately labeled with a unit to avoid any ambiguity. This means saying goodbye to using ordinary doubles and ints, every variable is now defined as a "Unit" or "Length" or "Meters", etc. Do people really do this on a large scale? So even if you have a large array, every element should be marked with a unit. This will obviously have both size and performance ramifications.

Despite all the cleverness in trying to push the unit logic into the background, some cumbersome notation seems inevitable with C#. F# does some behind-the-scenes magic that better reduces the annoyance factor of the unit logic.

Also, how successfully can we make the compiler treat a unit just like an ordinary double when we so desire, w/o using CType or ".Value" or any additional notation? Such as with nullables, the code knows to treat a double? just like a double (of course if your double? is null then you get an error).

Savour answered 9/2, 2011 at 21:54 Comment(1)
Yes, later comer to this discussion, but this is why I think this should best be a core language feature to assist developers rather than a code library, so that there are no abstractions remaining in the compiled code itself.Chicago
L
3

Thanks for the idea. I have implemented units in C# many different ways there always seems to be a catch. Now I can try one more time using the ideas discussed above. My goal is to be able to define new units based on existing ones like

Unit lbf = 4.44822162*N;
Unit fps = feet/sec;
Unit hp = 550*lbf*fps

and for the program to figure out the proper dimensions, scaling and symbol to use. In the end I need to build a basic algebra system that can convert things like (m/s)*(m*s)=m^2 and try to express the result based on existing units defined.

Also a requirement must be to be able to serialize the units in a way that new units do not need to be coded, but just declared in a XML file like this:

<DefinedUnits>
  <DirectUnits>
<!-- Base Units -->
<DirectUnit Symbol="kg"  Scale="1" Dims="(1,0,0,0,0)" />
<DirectUnit Symbol="m"   Scale="1" Dims="(0,1,0,0,0)" />
<DirectUnit Symbol="s"   Scale="1" Dims="(0,0,1,0,0)" />
...
<!-- Derived Units -->
<DirectUnit Symbol="N"   Scale="1" Dims="(1,1,-2,0,0)" />
<DirectUnit Symbol="R"   Scale="1.8" Dims="(0,0,0,0,1)" />
...
  </DirectUnits>
  <IndirectUnits>
<!-- Composite Units -->
<IndirectUnit Symbol="m/s"  Scale="1"     Lhs="m" Op="Divide" Rhs="s"/>
<IndirectUnit Symbol="km/h" Scale="1"     Lhs="km" Op="Divide" Rhs="hr"/>
...
<IndirectUnit Symbol="hp"   Scale="550.0" Lhs="lbf" Op="Multiply" Rhs="fps"/>
  </IndirectUnits>
</DefinedUnits>
Longs answered 16/11, 2010 at 20:13 Comment(2)
Not sure why you got a downvote, but I think you'd probably be better off learning F# than trying to re-invent the wheel. I've updated my question with a link to a paper that might interest you.Boot
Thanks for the constructive comment.Longs
M
1

there is jscience: http://jscience.org/, and here is a groovy dsl for units: http://groovy.dzone.com/news/domain-specific-language-unit-. iirc, c# has closures, so you should be able to cobble something up.

Microchemistry answered 8/12, 2008 at 7:55 Comment(0)
T
1

Why not use CodeDom to generate all possible permutations of the units automatically? I know it's not the best - but I will definitely work!

Tersina answered 8/12, 2008 at 8:31 Comment(0)
E
1

you could use QuantitySystem instead of implementing it by your own. It builds on F# and drastically improves unit handling in F#. It's the best implementation I found so far and can be used in C# projects.

http://quantitysystem.codeplex.com

Eve answered 30/3, 2014 at 20:21 Comment(0)
P
1

Is it worth using?

Yes. If I have "a number" in front of me, I want to know what that is. Any time of the day. Besides, that's what we usually do. We organize data into a meaningful entity -class, struct, you name it. Doubles into coordinates, strings into names and address etc. Why units should be any different?

Has someone else already done better?

Depends on how one defines "better". There are some libraries out there but I haven't tried them so I don't have an opinion. Besides it spoils the fun of trying it myself :)

Now about the implementation. I would like to start with the obvious: it's futile to try replicate the [<Measure>] system of F# in C#. Why? Because once F# allows you to use / ^ (or anything else for that matter) directly on another type, the game is lost. Good luck doing that in C# on a struct or class. The level of metaprogramming required for such a task does not exist and I'm afraid it is not going to be added any time soon -in my opinion. That's why you lack the dimensional analysis that Matthew Crumley mentioned in his answer.

Let's take the example from fsharpforfunandprofit.com: you have Newtons defined as [<Measure>] type N = kg m/sec^2. Now you have the square function that that the author created that will return a N^2 which sounds "wrong", absurd and useless. Unless you want to perform arithmetic operations where at some point during the evaluation process, you might get something "meaningless" until you multiply it with some other unit and you get a meaningful result. Or even worse, you might want to use constants. For example the gas constant R which is 8.31446261815324 J /(K mol). If you define the appropriate units, then F# is ready to consume the R constant. C# is not. You need to specify another type just for that and still you won't be able to do any operation you want on that constant.

That doesn't mean that you shouldn't try. I did and I am quite happy with the results. I started SharpConvert around 3 years ago, after I got inspired by this very question. The trigger was this story: once I had to fix a nasty bug for the RADAR simulator that I develop: an aircraft was plunging in the earth instead of following the predefined glide path. That didn't make me happy as you could guess and after 2 hours of debugging, I realized that somewhere in my calculations, I was treating kilometers as nautical miles. Until that point I was like "oh well I will just be 'careful'" which is at least naive for any non trivial task.

In your code there would be a couple of things I would do different.

First I would turn UnitDouble<T> and IUnit implementations into structs. A unit is just that, a number and if you want them to be treated like numbers, a struct is a more appropriate approach.

Then I would avoid the new T() in the methods. It does not invoke the constructor, it uses Activator.CreateInstance<T>() and for number crunching it will be bad as it will add overhead. That depends though on the implementation, for a simple units converter application it won't harm. For time critical context avoid like the plague. And don't take me wrong, I used it myself as I didn't know better and I run some simple benchmarks the other day and such a call might double the execution time -at least in my case. More details in Dissecting the new() constraint in C#: a perfect example of a leaky abstraction

I would also change Convert<T, R>() and make it a member function. I prefer writing

var c = new Unit<Length.mm>(123);
var e = c.Convert<Length.m>();

rather than

var e = Length.Convert<Length.mm, Length.m>(c);

Last but not least I would use specific unit "shells" for each physical quantity (length time etc) instead of the UnitDouble, as it will be easier to add physical quantity specific functions and operator overloads. It will also allow you to create a Speed<TLength, TTime> shell instead of another Unit<T1, T2> or even Unit<T1, T2, T3> class. So it would look like that:

public readonly struct Length<T> where T : struct, ILength
{
    private static readonly double SiFactor = new T().ToSiFactor;
    public Length(double value)
    {
        if (value < 0) throw new ArgumentException(nameof(value));
        Value = value;
    }

    public double Value { get; }

    public static Length<T> operator +(Length<T> first, Length<T> second)
    {
        return new Length<T>(first.Value + second.Value);
    }

    public static Length<T> operator -(Length<T> first, Length<T> second)
    {
        // I don't know any application where negative length makes sense,
        // if it does feel free to remove Abs() and the exception in the constructor
        return new Length<T>(System.Math.Abs(first.Value - second.Value));
    }
    
    // You can add more like
    // public static Area<T> operator *(Length<T> x, Length<T> y)
    // or
    //public static Volume<T> operator *(Length<T> x, Length<T> y, Length<T> z)
    // etc

    public Length<R> To<R>() where R : struct, ILength
    {
        //notice how I got rid of the Activator invocations by moving them in a static field;
        //double mult = new T().ToSiFactor;
        //double div = new R().ToSiFactor;
        return new Length<R>(Value * SiFactor / Length<R>.SiFactor);
    }
}

Notice also that, in order to save us from the dreaded Activator call, I stored the result of new T().ToSiFactor in SiFactor. It might seem awkward at first, but as Length is generic, Length<mm> will have its own copy, Length<Km> its own, and so on and so forth. Please note that ToSiFactor is the toBase of your approach.

The problem that I see is that as long as you are in the realm of simple units and up to the first derivative of time, things are simple. If you try to do something more complex, then you can see the drawbacks of this approach. Typing

var accel = new Acceleration<m, s, s>(1.2);

will not be as clear and "smooth" as

let accel = 1.2<m/sec^2>

And regardless of the approach, you will have to specify every math operation you will need with hefty operator overloading, while in F# you have this for free, even if the results are not meaningful as I was writing at the beginning.

The last drawback (or advantage depending on how you see it) of this design, is that it can't be unit agnostic. If there are cases that you need "just a Length" you can't have it. You need to know each time if your Length is millimeters, statute mile or foot. I took the opposite approach in SharpConvert and LengthUnit derives from UnitBase and Meters Kilometers etc derive from this. That's why I couldn't go down the struct path by the way. That way you can have:

LengthUnit l1 = new Meters(12);
LengthUnit l2 = new Feet(15.4);
LengthUnit sum = l1 + l2;

sum will be meters but one shouldn't care as long as they want to use it in the next operation. If they want to display it, then they can call sum.To<Kilometers>() or whatever unit. To be honest, I don't know if not "locking" the variable to a specific unit has any advantages. It might worth investigating it at some point.

Physics answered 12/5, 2021 at 21:11 Comment(2)
Wow! This was a trip down memory lane. Thanks for the interesting feedback. I recently had a similar problem that I solved (not very elegantly) with types - vector calculations for data from sources with different coordinate systems - I ended up with PointXY, PointYZ and PointXZ. Not pretty, but revealed several bugs.Boot
@Boot Haha, I hope that lane had good memories :) True it could be prettier but indeed it's good enough if it already revealed some bugs.Physics
S
1

I would like the compiler to help me as much as possible. So maybe you could have a TypedInt where T contains the actual unit.

    public struct TypedInt<T>
    {
        public int Value { get; }

        public TypedInt(int value) => Value = value;

        public static TypedInt<T> operator -(TypedInt<T> a, TypedInt<T> b) => new TypedInt<T>(a.Value - b.Value);
        public static TypedInt<T> operator +(TypedInt<T> a, TypedInt<T> b) => new TypedInt<T>(a.Value + b.Value);
        public static TypedInt<T> operator *(int a, TypedInt<T> b) => new TypedInt<T>(a * b.Value);
        public static TypedInt<T> operator *(TypedInt<T> a, int b) => new TypedInt<T>(a.Value * b);
        public static TypedInt<T> operator /(TypedInt<T> a, int b) => new TypedInt<T>(a.Value / b);

        // todo: m² or m/s
        // todo: more than just ints
        // todo: other operations
        public override string ToString() => $"{Value} {typeof(T).Name}";
    }

You could have an extensiom method to set the type (or just new):

    public static class TypedInt
    {
        public static TypedInt<T> Of<T>(this int value) => new TypedInt<T>(value);
    }

The actual units can be anything. That way, the system is extensible. (There's multiple ways of handling conversions. What do you think is best?)

    public class Mile
    {
        // todo: conversion from mile to/from meter
        // maybe define an interface like ITypedConvertible<Meter>
        // conversion probably needs reflection, but there may be
        // a faster way
    };

    public class Second
    {
    }

This way, you can use:

            var distance1 = 10.Of<Mile>();
            var distance2 = 15.Of<Mile>();
            var timespan1 = 4.Of<Second>();

            Console.WriteLine(distance1 + distance2);
            //Console.WriteLine(distance1 + 5); // this will be blocked by the compiler
            //Console.WriteLine(distance1 + timespan1); // this will be blocked by the compiler
            Console.WriteLine(3 * distance1);
            Console.WriteLine(distance1 / 3);
            //Console.WriteLine(distance1 / timespan1); // todo!
Socman answered 18/5, 2021 at 13:42 Comment(0)
Q
0

See Boo Ometa (which will be available for Boo 1.0): Boo Ometa and Extensible Parsing

Quadragesimal answered 28/1, 2009 at 22:7 Comment(2)
the link is bamboo.github.com/2008/08/05/…Pearson
fixed it, sorry I didn't see this sooner.Quadragesimal
D
0

I really liked reading through this stack overflow question and its answers.

I have a pet project that I've tinkered with over the years, and have recently started re-writing it and have released it to the open source at https://github.com/MafuJosh/NGenericDimensions

It happens to be somewhat similar to many of the ideas expressed in the question and answers of this page.

It basically is about creating generic dimensions, with the unit of measure and the native datatype as the generic type placeholders.

For example:

Dim myLength1 as New Length(of Miles, Int16)(123)

With also some optional use of Extension Methods like:

Dim myLength2 = 123.miles

And

Dim myLength3 = myLength1 + myLength2
Dim myArea1 = myLength1 * myLength2

This would not compile:

Dim myValue = 123.miles + 234.kilograms

New units can be extended in your own libraries.

These datatypes are structures that contain only 1 internal member variable, making them lightweight.

Basically, the operator overloads are restricted to the "dimension" structures, so that every unit of measure doesn't need operator overloads.

Of course, a big downside is the longer declaration of the generics syntax that requires 3 datatypes. So if that is a problem for you, then this isn't your library.

The main purpose was to be able to decorate an interface with units in a compile-time checking fashion.

There is a lot that needs to be done to the library, but I wanted to post it in case it was the kind of thing someone was looking for.

Decane answered 27/9, 2011 at 4:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.