Использование юнитов реального мира вместо типов - PullRequest
10 голосов
/ 22 октября 2010

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

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

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

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

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

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

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

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

Ответы [ 6 ]

10 голосов
/ 22 октября 2010

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

/// <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);
    }
1 голос
/ 22 октября 2010

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

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

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

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

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

0 голосов
/ 22 октября 2010

Я бы сделал Temperature абстрактный класс, который хранит температуру (в Кельвинах!) В свойстве InternalTempera.

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

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

0 голосов
/ 22 октября 2010

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

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

class PhysicalUnit
{
}

struct PhysicalValue
{
    readonly Value;
    readonly PhysicalUnit;
}

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

0 голосов
/ 22 октября 2010

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

struct Temperature 
{ 
    double _Value; 
} 
0 голосов
/ 22 октября 2010

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

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

...