C#中的度量单位 - 差不多

Ben*_*jol 57 c# f# units-of-measurement

受到F#中的度量单位的启发,尽管断言(这里)你无法用C#做到这一点,但我还有一个想法,那就是我一直在玩的.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

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
Run Code Online (Sandbox Code Playgroud)

下一步是尝试实现转换(代码段):

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

(我本来希望避免使用静态来实例化对象,但是我们都知道你不能在接口中声明静态方法)然后你可以这样做:

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

显然,与F#计量单位相比,这有一个巨大的漏洞(我会让你解决它).

哦,问题是:你怎么看待这个?值得使用吗?别人已经做得更好吗?

对这个主题领域感兴趣的人的更新,这里是1997年论文的链接,讨论了一种不同类型的解决方案(不是专门针对C#)

Mat*_*ley 41

您缺少尺寸分析.例如(从你链接到的答案),在F#中你可以这样做:

let g = 9.8<m/s^2>
Run Code Online (Sandbox Code Playgroud)

它将生成一个新的加速度单位,从米和秒中得出(你可以使用模板在C++中实际做同样的事情).

在C#中,可以在运行时进行维度分析,但它会增加开销,并且不会为您提供编译时检查的好处.据我所知,没有办法在C#中完成完整的编译时单元.

它是否值得做,取决于当然的应用,但对于许多科学应用,它绝对是一个好主意.我不知道.NET的任何现有库,但它们可能存在.

如果您对如何在运行时执行此操作感兴趣,我们的想法是每个值都有一个标量值和表示每个基本单元功能的整数.

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
            ...;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您不允许用户更改单位的值(无论如何都是个好主意),您可以为常用单位添加子类:

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
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

您还可以在派生类型上定义更多特定运算符,以避免检查常见类型上的兼容单元.


Dre*_*kes 18

您可以在数字类型上添加扩展方法以生成度量.它感觉有点像DSL:

var mass = 1.Kilogram();
var length = (1.2).Kilometres();
Run Code Online (Sandbox Code Playgroud)

它不是真正的.NET约定,可能不是最容易被发现的功能,所以也许你可以将它们添加到一个专门的命名空间中,供喜欢它们的人使用,以及提供更多传统的构造方法.


Mar*_*ade 17

对相同度量的不同单位使用单独的类(例如,长度为cm,mm和ft)似乎有点奇怪.基于.NET Framework的DateTime和TimeSpan类,我希望这样:

Length  length       = Length.FromMillimeters(n1);
decimal lengthInFeet = length.Feet;
Length  length2      = length.AddFeet(n2);
Length  length3      = length + Length.FromMeters(n3);
Run Code Online (Sandbox Code Playgroud)

  • 这也是我的第一直觉.这样做的缺点是你必须明确定义组合所有单位排列的运算符.当你开始将不同的单元组合在一起时会变得更加复杂,例如Velocity(Length/TimeSpan),你需要支持大量的FromXXX转换. (2认同)

Hen*_*ing 10

现在存在这样一个C#库:http: //www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

它具有与F#的单元编译时验证几乎相同的功能,但对于C#.核心是MSBuild任务,它解析代码并查找验证.

单位信息存储在注释和属性中.

  • 有趣的努力,但作者承认他对该项目不满意,并建议从头开始。一个类似的库可以在这里找到:https://github.com/InitialForce/UnitsNet (2认同)

ang*_*sen 10

我最近在GitHubNuGet上发布了Units.NET .

它为您提供所有常见的单位和转换.它重量轻,经过单元测试并支持PCL.

转换示例:

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
Run Code Online (Sandbox Code Playgroud)