Использование единиц реального мира вместо типов

У меня есть проект со многими вычислениями, включающими множество единиц реального мира:

  • Расстояние
  • Температура;
  • Расход,
  • ...

Этот проект включает сложные и многочисленные формулы расчета.

Вот почему я предположил, что использование настраиваемых типов, таких как Temperature, Distance..., может быть хорошим для чтения кода. Например:

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; для определения значения по умолчанию (I wand по умолчанию для двойного значения является NaN)
  • Они не могут наследовать от классов, они могут реализовать интерфейсы

Им интересно, есть ли способ сказать С#:

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

но я знаю, что С# не обрабатывает никаких настраиваемых единиц.

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

Итак, я предполагаю, что могу создать два класса Цельсия и Кельвина, которые наследуются от температуры, а затем я начал задаваться вопросом, действительно ли это стоит, потому что это связано с большим количеством кодирования и тестирования.

Это обсуждение, которое я хотел бы начать:

Будет ли использование реальных единиц в моем коде вместо типов .NET, было бы хорошо или нет? Кто-нибудь это уже? Каковы подводные камни и лучшие практики? Или мне лучше держаться подальше от этого и использовать стандартные типы .NET?

Ответ 1

Одним из способов достижения этой цели будет использование композиции основного объекта (Temperature в вашем случае) с классом TemperatureTraits, который специализируется на основном объекте. По аналогии с С++ эквивалентный класс String basic_string на самом деле является шаблоном класса (общим для терминов С#), который имеет параметры шаблона не только для строкового элемента (char, wide char), но и класса признаков который описывает, как класс ведет себя для определенного типа строкового элемента (например, char_traits).

В вашем случае вы можете определить общий тип, например

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

а затем реализация будет зависеть не только от измеряемого класса, но и от класса единиц. Насколько это было бы полезно на практике, будет зависеть от того, какая часть такого объекта может быть сделана действительно родовой - какие операции являются общими для комбинаций Measurable и Units?

В этом случае интересный документ, посвященный признакам С# , выглядит интересным.

Ответ 2

Почему бы не попробовать структуру, которая выглядит так:

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

Ответ 3

Я думаю, что это может быть полезно, когда вы хотите добавить более конкретные функции к температуре (например: IsFreezing()).

Чтобы решить проблему с помощью Kelvin и Celsius: создайте интерфейс ITemperature и базовый класс. В базовом классе вы можете реализовать интерфейс и заполнить детали, которые одинаковы для всех классов.

Ответ 4

Если вы используете структуру, то это не может быть null

struct Temperature 
{ 
    double _Value; 
} 

Ответ 5

Я не думаю, что стоит добавлять статические типы для единиц в С#. Вам нужно будет перегрузить так много операторов (для всех комбинаций устройств, а не только для всех блоков). И встраивайте в такие функции, как Math.Sqrt, работайте над нормальными удвоениями,...

Что вы можете попробовать использовать динамические типы:

class PhysicalUnit
{
}

struct PhysicalValue
{
    readonly Value;
    readonly PhysicalUnit;
}

И затем при компиляции в режиме отладки добавьте проверки, если единицы совпадают. И в релизе просто удалите поле PhysicalUnit и все проверки, и вы (почти) так же быстро, как и код, используя обычные удвоения.

Ответ 6

Я сделал бы Temperature абстрактным классом, который сохраняет температуру (в Kelvin!) в свойстве InternalTemperature.

Производный класс Celcius будет преобразовывать входное значение internaly в Kelvin. Он будет иметь свойство (readonly) Value, которое вернет внутреннее значение.

Сравнение их (одно более теплое, чем другое) было бы легко.