Units of measure in C# - almost

33,703

Solution 1

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.

Solution 2

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);

Solution 3

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.

Solution 4

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

Solution 5

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.

Share:
33,703
tina Miller
Author by

tina Miller

Saved from the web by a job in industry. Coding GUI for computer-aided railway realignment machinery. At last a job where I have time to do stuff properly and don't have to worry about loads of unfixed bugs coming back to haunt me.

Updated on July 08, 2022

Comments

  • tina Miller
    tina Miller almost 2 years

    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#)

  • tina Miller
    tina Miller over 15 years
    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.
  • tina Miller
    tina Miller over 13 years
    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.
  • John Alexiou
    John Alexiou over 13 years
    Thanks for the constructive comment.
  • Chris Kerekes
    Chris Kerekes over 12 years
    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.
  • Igor Pashchuk
    Igor Pashchuk over 12 years
  • justin.m.chase
    justin.m.chase over 12 years
    fixed it, sorry I didn't see this sooner.
  • Brian
    Brian almost 12 years
    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.
  • GKS
    GKS over 11 years
    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.
  • kmote
    kmote over 10 years
    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/UnitsNet
  • Dinoel Vokiniv
    Dinoel Vokiniv over 8 years
    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.
  • tina Miller
    tina Miller almost 3 years
    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.
  • Stelios Adamantidis
    Stelios Adamantidis almost 3 years
    @Benjol 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.