受到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 UnitDoublewhere T : IUnit { public readonly double Value; public UnitDouble(double value) { Value = value; } public static UnitDouble operator +(UnitDouble first, UnitDouble second) { return new UnitDouble (first.Value + second.Value); } //TODO: minus operator/equality } }
用法示例:
var a = new UnitDouble(3.1); var b = new UnitDouble (4.9); var d = new UnitDouble (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 UnitDoubleConvert (UnitDouble 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 (input.Value * mult / div); } }
(我本来希望避免使用静态来实例化对象,但是我们都知道你不能在接口中声明静态方法)然后你可以这样做:
var e = Length.Convert(c); var f = Length.Convert (d); <-- but not this
显然,与F#计量单位相比,这有一个巨大的漏洞(我会让你解决它).
哦,问题是:你怎么看待这个?值得使用吗?别人已经做得更好吗?
对这个主题领域感兴趣的人的更新,这里是1997年论文的链接,讨论了一种不同类型的解决方案(不是专门针对C#)
您缺少尺寸分析.例如(从你链接到的答案),在F#中你可以这样做:
let g = 9.8
它将生成一个新的加速度单位,从米和秒中得出(你可以使用模板在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 { } }
您还可以在派生类型上定义更多特定运算符,以避免检查常见类型上的兼容单元.
您可以在数字类型上添加扩展方法以生成度量.它感觉有点像DSL:
var mass = 1.Kilogram(); var length = (1.2).Kilometres();
它不是真正的.NET约定,可能不是最容易被发现的功能,所以也许你可以将它们添加到一个专门的命名空间中,供喜欢它们的人使用,以及提供更多传统的构造方法.
对相同度量的不同单位使用单独的类(例如,长度为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);
现在存在这样一个C#库:http: //www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp
它具有与F#的单元编译时验证几乎相同的功能,但对于C#.核心是MSBuild任务,它解析代码并查找验证.
单位信息存储在注释和属性中.
我最近在GitHub和NuGet上发布了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