Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Units of measure in C# - almost

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

like image 577
Benjol Avatar asked Dec 08 '08 07:12

Benjol


People also ask

What are the 7 unit of measurement?

Prefixes are provided in scales that function around 7 units known as metric system (or SI) base units. As shown in Table 1, base units include the meter (m), the kilogram (kg), the kelvin (K), the second (s), the ampere (A), the candela (cd), and the mole (mol).

What does C stand for measure?

Cup = C. or c. Pint = pt. Quart = qt. Gallon = gal.


1 Answers

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.

like image 118
Matthew Crumley Avatar answered Sep 19 '22 06:09

Matthew Crumley