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

发布于 2024-09-28 14:21:32 字数 1310 浏览 5 评论 0原文

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

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

本项目涉及计算公式复杂、数量众多。

这就是为什么我认为使用温度距离等自定义类型可以提高代码的可读性。例如:

Temperature x = -55.3;
Meter y = 3;

或者

var x = new Temperature(-55.3);

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

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; )来定义默认值(我希望默认的底层双精度值是NaN)
  • 它们不能从类继承,它们只能实现接口

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

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

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

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

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

这就是我想开始的讨论:

在我的代码中使用现实世界单位而不是 .NET 类型是好事还是坏事?有人已经这样了吗?有哪些陷阱和最佳实践?或者我应该最好远离这种情况并使用标准 .NET 类型?

I have a project with many calculations involving a lot of real world units :

  • Distance;
  • Temperature;
  • Flow rate;
  • ...

This project involves complicated and numerous calculation formulas.

That's why I supposed the use of custom types like Temperature, Distance... can be good for code readability. For example:

Temperature x = -55.3;
Meter y = 3;

or

var x = new Temperature(-55.3);

I tried to make a Temperature class that uses a double internal value.

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

But class are nullable. This mean that something like :

Temperature myTemp;

is "correct" and will be null. I dont want this. I dont want to use structs because they are too limited :

  • They cannot use parameterless constructor nor instance field intializers like double _Value = double.Nan; to define a default value (I wand default underlying double value to be NaN)
  • They cannot inherits from classes, they only can implement Interfaces

Them I wonder whether there is a way to tell C#:

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

but I know C# does not handle no custom units.

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

So I imagine I could create two Celsius and Kelvin classes that inherits from Temperature, and then I started to wonder if the idea really worth it, because it involves a lot of coding and testing.

That's the discussion I would like to start :

Would the use of real world units in my code instead of .NET types would be a good thing or not ? Did anyone this already ? What are the pitfalls and the best practices ? Or should I better keep stay away from this and use standard .NET types ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

小清晰的声音 2024-10-05 14:21:32

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

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

然后像这样使用它:

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

Why not try a struct that looks like this:

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

Then use it like, say, this:

    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);
    }
彼岸花似海 2024-10-05 14:21:32

实现此目的的一种方法是使用基本对象(在您的情况下为 Temperature)与专用于该基本对象的 TemperatureTraits 类的组合。与 C++ 类比,String 等价类 basic_string 实际上是一个类模板(C# 术语中的泛型),它不仅具有字符串元素(char、wide char),同时也是一个特征类,详细说明该类对于给定类型的字符串元素(例如 char_traits)的行为方式。

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

public class MeasurableWithUnits 的泛型

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

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

One way to achieve this would be to use composition of the basic object (Temperature in your case) with a TemperatureTraits class that specializes the basic object. By analogy to C++, the String equivalent class basic_string is actually a class template (generic in C# terms) that has template parameters not only for the string element (char, wide char) but also a traits class that elaborates on how the class behaves for a given type of string element (e.g. char_traits).

In your case, you might define a generic like

public class MeasurableWithUnits<class M MEASURABLE, class U UNITS>

and then implementation would depend not only on the measurable class but also on the units class. How useful this would be in practice would depend on how much of such an object could be made truly generic - what operations are common across combinations of Measurable and Units?

There is a research paper on C# traits here, if this approach looks interesting.

↘人皮目录ツ 2024-10-05 14:21:32

我认为当您想向温度添加更多特定功能时(例如:IsFreezing()),它可能会很好。

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

I think it can be good when you want to add more specific functionality to a temperature (for example: IsFreezing()).

To solve the issue with Kelvin and Celsius: make an interface ITemperature and a baseclass. In the baseclass, you can implement the interface and fill in the details that are the same for all classes.

给我一枪 2024-10-05 14:21:32

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

struct Temperature 
{ 
    double _Value; 
} 

If you use a struct, then this cannot be null

struct Temperature 
{ 
    double _Value; 
} 
溺深海 2024-10-05 14:21:32

我认为不值得在 C# 中为单元添加静态类型。您将需要重载如此多的运算符(对于所有单位组合,而不仅仅是所有单位)。并且像 Math.Sqrt 这样的函数可以在普通双精度上工作,...

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

class PhysicalUnit
{
}

struct PhysicalValue
{
    readonly Value;
    readonly PhysicalUnit;
}

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

I don't think it's worth adding static types for units in C#. You would need to overload so many operators(for all unit combinations, not just for all units). And build in functions like Math.Sqrt work on normal doubles,...

What you might try is using dynamic types:

class PhysicalUnit
{
}

struct PhysicalValue
{
    readonly Value;
    readonly PhysicalUnit;
}

And then when compiling in debug mode add checks if the units fit together. And in release just remove the PhysicalUnit field and all the checks, and you're (almost) as fast as code using normal doubles.

肩上的翅膀 2024-10-05 14:21:32

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

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

然后比较它们(一个比另一个温暖)就会很容易。

I would make Temperature an abstract class that stores the temperature (in Kelvin!) in an InternalTemperature property.

A derived class Celcius would translate the input value internaly to Kelvin. It would have a (readonly) Value property that translated the internal value back.

Comparing them (is one warmer than the other) would then be easy.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文