Использование единиц реального мира вместо типов
У меня есть проект со многими вычислениями, включающими множество единиц реального мира:
- Расстояние
- Температура;
- Расход,
- ...
Этот проект включает сложные и многочисленные формулы расчета.
Вот почему я предположил, что использование настраиваемых типов, таких как 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, которое вернет внутреннее значение.
Сравнение их (одно более теплое, чем другое) было бы легко.