在C#中的度量单位 – 差不多

受F#中的度量单位的启发,尽pipe( 在这里 )断言你不能用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); } } 

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

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

显然,这里有一个漏洞,相比F#单位的度量(我会让你解决)。

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

更新对这个主题感兴趣的人, 这里是一个从1997年的论文链接讨论一种不同的解决scheme(不是专门为C#)

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

 let g = 9.8<m/s^2> 

它会产生一个新的加速度单位,从米和秒(你可以在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 ...; } } 

如果你不允许用户改变单位的值(反正是个好主意),你可以添加普通单位的子类:

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

您也可以在派生types上定义更具体的操作符,以避免检查常见types的兼容单元。

您可以在数字types上添加扩展方法来生成度量。 它会觉得有点DSL:

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

这不是真正的.NET惯例,也许不是最容易被发现的特性,所以也许你会把它们join到一个专门的命名空间中,以供喜欢它们的人使用,并提供更传统的构造方法。

使用不同单位的不同单位的相同的措施(例如,厘米,毫米,英尺长度)似乎有点怪异。 基于.NET Framework的DateTime和TimeSpan类,我期望如下所示:

 Length length = Length.FromMillimeters(n1); decimal lengthInFeet = length.Feet; Length length2 = length.AddFeet(n2); Length length3 = length + Length.FromMeters(n3); 

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

它具有与F#的单元编译时间validation几乎相同的function,但是对于C#。 核心是一个MSBuild任务,parsing代码并寻找validation。

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

我最近在GitHub和NuGet上发布了Units.NET。

它给你所有的通用单位和转换。 这是重量轻,unit testing和支持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 

感谢您的想法。 我用C#实现了许多不同的方式,总是有一个问题。 现在,我可以再次尝试使用上面讨论的想法。 我的目标是能够基于现有的定义新单位

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

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

另外一个要求是必须能够以新单元不需要编码的方式对单元进行序列化,而只需要像这样在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> 

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

我觉得这个想法非常有吸引力,但每次我都不敢肯定这个项目的一个巨大的步骤。 它看起来像一个全或无的交易。 你可能不会把几个数字换成单位, 总的来说,一个项目中的所有数据都用一个单位进行了适当的标记,以避免模棱两可。 这意味着告别使用普通的双打和整数,每个variables现在被定义为“单位”或“长度”或“米”等。人们是否真的做到这一点大规模? 所以即使你有一个大的数组,每个元素都应该用一个单位来标记。 这显然具有尺寸和性能方面的影响。

尽pipe试图将单元逻辑推入后台,但有些繁琐的表示方式似乎不可避免的使用C#。 F#做了一些幕后的魔法,更好的减less了单位逻辑的烦恼因素。

另外,如果我们愿意的话,如何使用CType或“.Value”或任何附加的符号,我们可以如何成功地让编译器像普通的double一样对待一个单元呢? 比如用空字符,代码就知道要对待双精度? 就像一个双(当然,如果你的双?是空的,那么你会得到一个错误)。

有jscience: http ://jscience.org/,这里是一个groovy dsl的单位: http ://groovy.dzone.com/news/domain-specific-language-unit-。 iirc,c#已经closures,所以你应该可以修补一些东西。

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

请参阅Boo Ometa(可用于Boo 1.0): Boo Ometa和Extensible Parsing

我真的很喜欢通过这个堆栈溢出问题和答案阅读。

我有一个多年来一直在琢磨着的宠物项目,最近开始重写它,并将其发布到http://ngenericdimensions.codeplex.com的开放源码中。;

它恰好与这个页面的问题和答案中expression的许多想法有些相似。

它基本上是关于创build通用维度,以度量单位和本地数据types作为通用types占位符。

例如:

 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 

新的单位可以扩展到你自己的图书馆。

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

基本上,操作符重载限制在“维度”结构中,因此每个度量单位都不需要操作符重载。

当然,一个很大的缺点是generics语法的声明需要3个数据types。 所以,如果这对你来说是一个问题,那么这不是你的图书馆。

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

图书馆还有很多需要做的事情,但是我想把它发布,以防某些人在寻找。

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

http://quantitysystem.codeplex.com