关于c#:使用真实世界单位而不是类型

Using real world units instead of types

我有一个项目,有很多计算涉及很多实际单位:

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

本工程计算公式复杂、多。

这就是为什么我要使用自定义类型,如温度、距离…对代码可读性很好。例如:

1
2
Temperature x = -55.3;
Meter y = 3;

1
var x = new Temperature(-55.3);

我尝试创建一个使用双内部值的温度类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
    }
}

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

1
Temperature myTemp;

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

  • 它们不能使用无参数构造函数或实例字段初始化器(如double _Value = double.Nan;来定义默认值(i将默认的基础双值指定为NaN)
  • 它们不能从类继承,只能实现接口

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

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

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

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

所以我想我可以创建两个从温度继承的摄氏度和开尔文类,然后我开始怀疑这个想法是否真的值得,因为它涉及很多编码和测试。

这就是我想开始的讨论:

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


为什么不尝试这样的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/// <summary>
/// Temperature class that uses a base unit of Celsius
/// </summary>
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; }
    }
}

然后像这样使用它,比如说:

1
2
3
4
5
6
7
8
9
    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);
    }


实现这一点的一种方法是使用基本对象的组合(在您的例子中是Temperature)和专门用于基本对象的TemperatureTraits类。通过类似于C++,EDCOX1和7的等价类EDCOX1〔8〕实际上是一个类模板(C语言中的泛型),它不仅具有字符串元素的模板参数(EDCOX1,9,宽char),而且是一个特性类,它详细说明了该类如何对给定类型的字符串元素(例如EDCOX1(10))进行行为。

在您的案例中,您可以定义一个

江户十一〔11〕。

然后,实现不仅取决于可度量类,还取决于单位类。这在实践中有多有用取决于这种对象中有多少是真正通用的——在MeasurableUnits的组合中,哪些操作是常见的?

如果这种方法看起来有趣的话,这里有一篇关于C特性的研究论文。


我会把Temperature作为一个抽象类来存储温度(开尔文!)内部温度属性。

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

比较它们(一个比另一个暖和)会很容易。


我认为不值得为C中的单元添加静态类型。您需要过载这么多的操作符(对于所有单元组合,而不仅仅是对于所有单元)。以及内置函数,比如math.sqrt在普通双精度上工作,…

您可以尝试使用动态类型:

1
2
3
4
5
6
7
8
9
class PhysicalUnit
{
}

struct PhysicalValue
{
    readonly Value;
    readonly PhysicalUnit;
}

然后在调试模式下编译时,添加检查单元是否匹配。在发行版中,只需去掉physicalunit字段和所有的检查,您(几乎)和使用普通双精度代码的速度一样快。


如果使用结构,则不能是EDOCX1[2]

1
2
3
4
struct Temperature
{
    double _Value;
}

我认为当你想在一个温度上增加更具体的功能时(例如:IsFreezing())会更好。

用开尔文和摄氏度来解决这个问题:创建一个接口ITemperature和一个基类。在基类中,您可以实现接口并填写所有类的相同细节。