Units of measure in C# - almost

后端 未结 12 1304
再見小時候
再見小時候 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
    

    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.

提交回复
热议问题