代码之家  ›  专栏  ›  技术社区  ›  Benjol

以C#-几乎

  •  57
  • Benjol  · 技术社区  · 16 年前

    Units of Measure in F# ,尽管断言( here )你不能在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
        }
    }
    

    示例用法:

    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
    

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

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

    (我本想通过使用static来避免实例化对象,但是我们都知道 can't declare a static method in an interface ) 然后您可以执行以下操作:

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

    哦,问题是:你觉得这个怎么样?值得使用吗?其他人已经做得更好了吗?

    更新 here 链接到1997的一篇文章,讨论了一种不同的解决方案(不是专门针对Cé的)

    12 回复  |  直到 7 年前
        1
  •  41
  •   Matthew Crumley    13 年前

    您缺少维度分析。例如(从链接到的答案),在F中可以执行以下操作:

    let g = 9.8<m/s^2>
    

    它将产生一个新的加速度单位,从米和秒导出(实际上你可以用模板在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
                ...;
        }
    }
    

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

    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
        {
        }
    }
    

    还可以在派生类型上定义更具体的运算符,以避免检查公共类型上的兼容单元。

        2
  •  18
  •   Drew Noakes    14 年前

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

    var mass = 1.Kilogram();
    var length = (1.2).Kilometres();
    

    它不是真正的.NET约定,也可能不是最容易被发现的功能,所以也许您可以将它们添加到专门为喜欢它们的人提供的命名空间中,并提供更常规的构造方法。

        3
  •  17
  •   Mark Cidade    16 年前

    对同一度量单位的不同单位使用不同的类(例如,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);
    
        4
  •  11
  •   Henning    12 年前

    http://www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

    它与F的单元编译时验证功能几乎相同,但对于C。 核心是一个MSBuild任务,它解析代码并寻找验证。

    单元信息存储在注释和属性中。

        5
  •  10
  •   angularsen Bcelik    7 年前

    GitHub 以及 NuGet .

    它给你所有的共同单位和转换。它重量轻,单元测试,支持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
    
        6
  •  4
  •   Scott    13 年前

    我关心的是用C#/VB创建单元。如果你认为我错了,请纠正我。我读过的大多数实现似乎都涉及到创建一个将一个值(int或double)与一个单元组合在一起的结构。然后,尝试为这些结构定义基本函数(+-*/,等等),其中考虑了单位转换和一致性。

    尽管试图把单元逻辑推向背景,但C语言中的一些繁琐的符号似乎是不可避免的。F#做一些幕后魔术,更好地减少单位逻辑的烦恼因素。

    另外,如果我们不使用CType或“.Value”或任何附加符号,那么如何成功地使编译器像对待普通的double一样对待一个单元呢?例如对于空值,代码知道如何处理double吗?就像一个双人间(当然,如果你的双人间?如果为空,则会出现错误)。

        7
  •  3
  •   John Alexiou    14 年前

    谢谢你的主意。我已经用C#很多不同的方法实现了单元,似乎总有一个陷阱。现在我可以用上面讨论的想法再试一次。我的目标是能够基于现有的类来定义新的单元。

    Unit lbf = 4.44822162*N;
    Unit fps = feet/sec;
    Unit hp = 550*lbf*fps
    

    并为程序找出合适的尺寸、比例和符号来使用。最后,我需要建立一个基本的代数系统,它可以转换像 (m/s)*(m*s)=m^2 并尝试根据现有的单位定义结果。

    此外,还必须能够以不需要对新单元进行编码的方式序列化单元,而只需在如下XML文件中声明:

    <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>
    
        8
  •  1
  •   Ray Tayek    16 年前

    http://jscience.org/ ,下面是一个groovy dsl单元: http://groovy.dzone.com/news/domain-specific-language-unit- . iirc,c有闭包,所以你应该可以拼凑一些东西。

        9
  •  1
  •   Jonathan C Dickinson    16 年前

    为什么不使用CodeDom自动生成所有可能的单元排列呢?我知道这不是最好的-但我一定会工作!

        10
  •  1
  •   MovGP0    10 年前

    你可以使用QuantitySystem而不是自己实现它。它建立在F#的基础上,极大地提高了F#的单位处理能力。这是迄今为止我发现的最好的实现,可以用于C#项目。

    http://quantitysystem.codeplex.com

        11
  •  0
  •   justin.m.chase    13 年前
        12
  •  0
  •   Simon Dugré Ratna Halder    12 年前

    我非常喜欢阅读这个堆栈溢出问题及其答案。

    我有一个宠物项目,多年来我一直在修改,最近开始重新编写,并将其发布到 http://ngenericdimensions.codeplex.com

    它基本上是关于创建泛型维度,使用度量单位和本机数据类型作为泛型类型占位符。

    例如:

    Dim myLength1 as New Length(of Miles, Int16)(123)
    

    Dim myLength2 = 123.miles
    

    以及

    Dim myLength3 = myLength1 + myLength2
    Dim myArea1 = myLength1 * myLength2
    

    Dim myValue = 123.miles + 234.kilograms
    

    新单元可以在您自己的库中扩展。

    这些数据类型是只包含1个内部成员变量的结构,使它们成为轻量级的。

    基本上,运算符重载仅限于“维度”结构,因此每个度量单位都不需要运算符重载。

    其主要目的是能够以编译时检查的方式修饰与单元的接口。

    图书馆有很多事情要做,但我想把它寄出去,以防有人在找这种东西。