Units of measure in C# - almost

后端 未结 12 1235
再見小時候
再見小時候 2020-11-27 10:29

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.

         


        
相关标签:
12条回答
  • 2020-11-27 10:51

    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.

    0 讨论(0)
  • 2020-11-27 10:51

    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.

    0 讨论(0)
  • 2020-11-27 10:57

    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
    
    0 讨论(0)
  • 2020-11-27 10:58

    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);
    
    0 讨论(0)
  • 2020-11-27 10:59

    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>
    
    0 讨论(0)
  • 2020-11-27 11:01

    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

    0 讨论(0)
提交回复
热议问题