C# 为了清晰起见,使用结构而不是浮动

C# 为了清晰起见,使用结构而不是浮动,c#,struct,C#,Struct,我们正在开发一个涉及船只的应用程序。在内部,我们的代码以米/秒(m/s)为单位测量速度,但在用户界面中,它以节为单位显示。速度值(以m/s或节为单位)存储在浮点数中 float speedKnots = speedSlider.value; float speedMS = Units.KnotsToMetersPerSecond(speedKnots); boat.Speed = speedMS; 尽管我们尽了最大努力使用良好的命名约定,团队成员(包括我自己)偶尔会忘记在m/s和结之间转换,例

我们正在开发一个涉及船只的应用程序。在内部,我们的代码以米/秒(m/s)为单位测量速度,但在用户界面中,它以节为单位显示。速度值(以m/s或节为单位)存储在浮点数中

float speedKnots = speedSlider.value;
float speedMS = Units.KnotsToMetersPerSecond(speedKnots);
boat.Speed = speedMS;
尽管我们尽了最大努力使用良好的命名约定,团队成员(包括我自己)偶尔会忘记在m/s和结之间转换,例如

boat.Speed = speedSlider.Value; //bad code - forgot the unit conversion!
查找和修复这些问题可能非常耗时。我想到的一个解决方案是为只包装浮动的
MetersPerSecond
Knots
创建结构:

public struct Knots {
    public Knots(float value) { this.Value = value; }
    public float Value { get; set; }
    public static implicit operator MetersPerSecond(Knots k) => k.Value * 0.514444;
    public static implicit operator float(Knots k) => k.Value;
    //other operator overloading omitted for brevity
}
public struct MetersPerSecond {
    public MetersPerSecond(float value) { this.Value = value; }
    public float Value { get; set; }
    public static implicit operator Knots(MetersPerSecond mps) => mps.Value * 1.94384;
    public static implicit operator float(MetersPerSecond mps) => mps.Value;
    //other operator overloading omitted for brevity
}

对我来说,这是一种更安全、更明确的方法。然而,我担心这是过度工程。我记不起曾经在任何语言中使用过这种策略。标准做法似乎只是使用内置的数字类型,如
float
,并记住调用单位转换函数


创建包含单个数值的结构并进行隐式类型转换,只是为了确保不会忘记单元转换,这是一种糟糕的编程实践吗?有没有考虑到哪些副作用可能不那么明显?

< P>一个选项可能是创建一个结构,它使用一个或另一个度量单位作为“基础”,然后在需要时总是将该度量单位转换为另一个度量单位。重要的是,基本单位是我们可以用于比较、加法、减法等的单位

注意:在进行比较时,我们应该使用整数或十进制作为基类型,因为浮点和双精度是二进制浮点类型,会导致舍入错误

我们可以添加一些基于我们想要的任何度量单位创建结构实例的
static
方法,以及一些
add
Subtract
方法,这些方法通过将基本单位相加来返回结构的新实例。我们还可以覆盖常见的数学运算符

下面是一个使用
MetersPerSecond
作为基本单位的示例,但它可以从米/秒或节创建:

public struct Speed : IEquatable<Speed>, IComparable<Speed>
{
    // This is the "base" unit that will be used for comparisons with other instances
    public decimal MetersPerSecond { get; private set; }
    public decimal Knots => MetersPerSecond * MpsToKnot;

    private const decimal MpsToKnot = 1.943844M;

    // Static methods to create instances of this class
    public static Speed FromMetersPerSecond(decimal metersPerSecond)
    {
        return new Speed { MetersPerSecond = metersPerSecond};
    }

    public static Speed FromKnots(decimal knots)
    {
        return FromMetersPerSecond(knots / MpsToKnot);
    }

    // Mathematic and comparison methods and operators
    public Speed Add(Speed speed)
    {
        return new Speed { MetersPerSecond = this.MetersPerSecond + speed.MetersPerSecond };
    }

    public Speed Subtract(Speed speed)
    {
        return new Speed { MetersPerSecond = this.MetersPerSecond - speed.MetersPerSecond };
    }

    public int CompareTo(Speed other)
    {
        return MetersPerSecond.CompareTo(other.MetersPerSecond);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Speed)) return false;
        return Equals((Speed) obj);
    }

    public bool Equals(Speed other)
    {
        return MetersPerSecond.Equals(other.MetersPerSecond);
    }

    public override int GetHashCode()
    {
        return MetersPerSecond.GetHashCode();
    }

    public static Speed operator -(Speed s)
    {
        return new Speed { MetersPerSecond = -s.MetersPerSecond };
    }

    public static Speed operator -(Speed s1, Speed s2)
    {
        return s1.Subtract(s2);
    }

    public static Speed operator +(Speed s)
    {
        return s;
    }

    public static Speed operator +(Speed s1, Speed s2)
    {
        return s1.Add(s2);
    }

    public static bool operator ==(Speed s1, Speed s2)
    {
        return s1.MetersPerSecond == s2.MetersPerSecond;
    }

    public static bool operator !=(Speed s1, Speed s2)
    {
        return s1.MetersPerSecond != s2.MetersPerSecond;
    }

    public static bool operator <(Speed s1, Speed s2)
    {
        return s1.MetersPerSecond < s2.MetersPerSecond;
    }

    public static bool operator <=(Speed s1, Speed s2)
    {
        return s1.MetersPerSecond <= s2.MetersPerSecond;
    }

    public static bool operator >(Speed s1, Speed s2)
    {
        return s1.MetersPerSecond > s2.MetersPerSecond;
    }

    public static bool operator >=(Speed s1, Speed s2)
    {
        return s1.MetersPerSecond >= s2.MetersPerSecond;
    }
}
现在,在指定
速度时,我们必须明确测量单位:

boat.Speed = Speed.FromKnots(speedSlider.value);

我希望这个问题不是太主观,但我意识到可能没有一个广泛认同的答案。旁注:双向隐式转换运算符是个坏主意。至少有一个方向应该是明确的。寻找更可能导致精度损失的转换,并将其明确化。如果存在相同的精度损失可能性,它们都应该是显式的。另一种选择是选择一个值作为标准值,并将值解析为您的类型<代码>船速从结(浮结)
船速从米秒秒(浮点数)
和格式到所需的单位
浮点数()
,和
浮点数()
,无论您如何在内部存储它。“我记不起在任何语言中使用过这种策略。”-…GitHub上有一个名为的库,它同时定义了“米/秒”和“结”单位(参见文件)。我个人没有这方面的经验,但它可能在这里派上用场。这是一个很好的策略,我最终也做了类似的事情。需要注意的一点是,对于大多数类型的软件来说,十进制的额外精度太高了,与浮点或双精度相比,性能成本非常高。大多数应用程序不会在它上面出现瓶颈,但它仍然值得注意。@Kevin是的,我没有用
decimal
来衡量性能,我用它来做相等比较。使用带有二进制浮点值的
=
无法获得准确的结果。如果有某种方法将“基本”度量单位表示为
,那可能会更好。公平点。我认为在我们的例子中,唯一需要进行等式比较的时间是
if(speed.MetersPerSecond==0)
。仅供参考,我使用浮点数是因为它们对于我们的目的来说足够精确,我们的引擎(Unity)在浮点数中完成所有操作。当我需要比较浮点等式时,我经常做模糊等式(
if Math.Abs(a-b)
boat.Speed = Speed.FromKnots(speedSlider.value);