使用真实世界单位而不是类型

我有一个涉及许多现实世界单位的计算项目:

  • 距离;
  • 温度;
  • 流量;

该项目涉及复杂而众多的计算公式。

这就是为什么我认为使用像TemperatureDistance ……这样的自定义类型可以提高代码的可读性。 例如:

Temperature x = -55.3; Meter y = 3; 

要么

 var x = new Temperature(-55.3); 

我试图制作一个使用双内部值的Temperature类。

 public class Temperature { double _Value = double.NaN; public Temperature() { } public Temperature(double v) { _Value = v; } public static implicit operator Temperature(double v) { return new Temperature(v); } } 

但是类可以为空。 这意味着:

 Temperature myTemp; 

是“正确的”,将为空。 我不想要这个。 我不想使用结构,因为它们太有限了:

  • 他们不能使用无参数构造函数或实例字段初始化器,如double _Value = double.Nan; 定义默认值(我将默认的底层double值设为NaN)
  • 它们不能从类inheritance,它们只能实现接口

我想知道是否有办法告诉C#:

 Temperature myTemp = 23K; // C# does not implement anything to make K unit... 

但我知道C#不处理任何自定义单位。

 Temperature myTemp = new Kelvin(23); // This might work 

所以我想我可以创建两个inheritance温度的Celsius和Kelvin类,然后我开始怀疑这个想法是否真的值得,因为它涉及大量的编码和测试。

这是我想要开始的讨论:

在我的代码中使用真实世界单元而不是.NET类型是否是一件好事? 这个人已经有了吗? 有哪些陷阱和最佳做法? 或者我应该更好地远离这个并使用标准的.NET类型?

实现此目的的一种方法是使用基本对象的组合(在您的情况下为TemperatureTraits )和专门用于基本对象的TemperatureTraits类。 类似于C ++, String等效类basic_string实际上是一个类模板(C#术语中的generics),它不仅具有模板参数,而且还包含字符串元素( char ,wide char),还有一个traits类,详细说明了类的行为方式给定类型的字符串元素(例如char_traits )。

在您的情况下,您可以定义类似的generics

public class MeasurableWithUnits

然后实施不仅取决于可测量的类,还取决于单元类。 这在实践中有多大用处取决于这样一个对象有多少可以真正通用 – 在MeasurableUnits组合中哪些操作是共同的?

如果这种方法看起来很有趣,那么这里有一篇关于C#特征的研究论文。

为什么不尝试看起来像这样的结构:

 ///  /// Temperature class that uses a base unit of Celsius ///  public struct Temp { public static Temp FromCelsius(double value) { return new Temp(value); } public static Temp FromFahrenheit(double value) { return new Temp((value - 32) * 5 / 9); } public static Temp FromKelvin(double value) { return new Temp(value - 273.15); } public static Temp operator +(Temp left, Temp right) { return Temp.FromCelsius(left.Celsius + right.Celsius); } private double _value; private Temp(double value) { _value = value; } public double Kelvin { get { return _value + 273.15; } } public double Celsius { get { return _value; } } public double Fahrenheit { get { return _value / 5 * 9 + 32; } } } 

然后使用它,比如说:

  static void Main(string[] args) { var c = Temp.FromCelsius(30); var f = Temp.FromFahrenheit(20); var k = Temp.FromKelvin(20); var total = c + f + k; Console.WriteLine("Total temp is {0}F", total.Fahrenheit); } 

我想当你想为温度添加更多特定function时可能会很好(例如: IsFreezing() )。

要解决Kelvin和Celsius的问题:创建一个接口ITemperature和一个基类。 在基类中,您可以实现接口并填写所有类相同的详细信息。

如果使用结构,则不能为null

 struct Temperature { double _Value; } 

我不认为在C#中为单位添加静态类型是值得的。 您需要重载这么多运算符(对于所有单位组合,而不仅仅是所有单位)。 并构建像Math.Sqrt这样的函数,正常双打,…

你可能会尝试使用动态类型:

 class PhysicalUnit { } struct PhysicalValue { readonly Value; readonly PhysicalUnit; } 

然后在调试模式下编译时,添加检查单元是否适合。 在发布中只需删除PhysicalUnit字段和所有检查,您(几乎)与使用普通双精度的代码一样快。

我将使Temperature成为一个抽象类,它将温度(以开尔文为单位)存储在InternalTemperature属性中。

派生类Celcius会将输入值内部转换为开尔文。 它将具有(只读)Value属性,可以将内部值转换回来。

比较它们(比一个更温暖)将很容易。