Единицы измерения в C # - почти - PullRequest
57 голосов
/ 08 декабря 2008

Вдохновленный Единицами измерения в F # , и, несмотря на утверждение ( здесь ), что вы не могли сделать это в C #, у меня была идея на днях, что я играл с.

namespace UnitsOfMeasure
{
    public interface IUnit { }
    public static class Length
    {
        public interface ILength : IUnit { }
        public class m : ILength { }
        public class mm : ILength { }
        public class ft : ILength { }
    }
    public class Mass
    {
        public interface IMass : IUnit { }
        public class kg : IMass { }
        public class g : IMass { }
        public class lb : IMass { }
    }

    public class UnitDouble<T> where T : IUnit
    {
        public readonly double Value;
        public UnitDouble(double value)
        {
            Value = value;
        }
        public static UnitDouble<T> operator +(UnitDouble<T> first, UnitDouble<T> second)
        {
            return new UnitDouble<T>(first.Value + second.Value);
        }
        //TODO: minus operator/equality
    }
}

Пример использования:

var a = new UnitDouble<Length.m>(3.1);
var b = new UnitDouble<Length.m>(4.9);
var d = new UnitDouble<Mass.kg>(3.4);
Console.WriteLine((a + b).Value);
//Console.WriteLine((a + c).Value); <-- Compiler says no

Следующий шаг - попытка реализовать преобразования (фрагмент):

public interface IUnit { double toBase { get; } }
public static class Length
{
    public interface ILength : IUnit { }
    public class m : ILength { public double toBase { get { return 1.0;} } }
    public class mm : ILength { public double toBase { get { return 1000.0; } } }
    public class ft : ILength { public double toBase { get { return 0.3048; } } }
    public static UnitDouble<R> Convert<T, R>(UnitDouble<T> input) where T : ILength, new() where R : ILength, new()
    {
        double mult = (new T() as IUnit).toBase;
        double div = (new R() as IUnit).toBase;
        return new UnitDouble<R>(input.Value * mult / div);
    }
}

(Я бы хотел избежать создания экземпляров объектов с помощью static, но, как мы все знаем, вы не можете объявить статический метод в интерфейсе ) Затем вы можете сделать это:

var e = Length.Convert<Length.mm, Length.m>(c);
var f = Length.Convert<Length.mm, Mass.kg>(d); <-- but not this

Очевидно, в этом есть зияющая дыра по сравнению с F # Единицами измерения (я позволю вам разобраться).

О, вопрос: что вы думаете об этом? Это стоит использовать? Кто-то уже сделал лучше?

ОБНОВЛЕНИЕ для людей, заинтересованных в этой предметной области, здесь - ссылка на статью 1997 года, в которой обсуждается другой тип решения (не специально для C #)

Ответы [ 12 ]

41 голосов
/ 08 декабря 2008

Вам не хватает анализа размеров. Например (из ответа, на который вы ссылаетесь), в F # вы можете сделать это:

let g = 9.8<m/s^2>

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

В C # можно выполнять анализ измерений во время выполнения, но это увеличивает накладные расходы и не дает преимуществ проверки во время компиляции. Насколько я знаю, в C # нет способа делать полные модули времени компиляции.

Стоит ли это делать, конечно, зависит от области применения, но для многих научных приложений это определенно хорошая идея. Я не знаю ни одной существующей библиотеки для .NET, но они, вероятно, существуют.

Если вас интересует, как это сделать во время выполнения, идея состоит в том, что каждое значение имеет скалярное значение и целые числа, представляющие мощность каждой базовой единицы.

class Unit
{
    double scalar;
    int kg;
    int m;
    int s;
    // ... for each basic unit

    public Unit(double scalar, int kg, int m, int s)
    {
       this.scalar = scalar;
       this.kg = kg;
       this.m = m;
       this.s = s;
       ...
    }

    // For addition/subtraction, exponents must match
    public static Unit operator +(Unit first, Unit second)
    {
        if (UnitsAreCompatible(first, second))
        {
            return new Unit(
                first.scalar + second.scalar,
                first.kg,
                first.m,
                first.s,
                ...
            );
        }
        else
        {
            throw new Exception("Units must match for addition");
        }
    }

    // For multiplication/division, add/subtract the exponents
    public static Unit operator *(Unit first, Unit second)
    {
        return new Unit(
            first.scalar * second.scalar,
            first.kg + second.kg,
            first.m + second.m,
            first.s + second.s,
            ...
        );
    }

    public static bool UnitsAreCompatible(Unit first, Unit second)
    {
        return
            first.kg == second.kg &&
            first.m == second.m &&
            first.s == second.s
            ...;
    }
}

Если вы не разрешаете пользователю изменять значение единиц (в любом случае, это хорошая идея), вы можете добавить подклассы для общих единиц:

class Speed : Unit
{
    public Speed(double x) : base(x, 0, 1, -1, ...); // m/s => m^1 * s^-1
    {
    }
}

class Acceleration : Unit
{
    public Acceleration(double x) : base(x, 0, 1, -2, ...); // m/s^2 => m^1 * s^-2
    {
    }
}

Вы также можете определить более специфические операторы для производных типов, чтобы избежать проверки совместимых единиц для общих типов.

18 голосов
/ 01 июня 2010

Вы можете добавить методы расширения для числовых типов для генерации мер. Это будет немного похоже на DSL:

var mass = 1.Kilogram();
var length = (1.2).Kilometres();

Это на самом деле не соглашение .NET и, возможно, не самая доступная функция, поэтому, возможно, вы добавите их в специальное пространство имен для людей, которым они нравятся, а также предложите более традиционные методы построения.

17 голосов
/ 08 декабря 2008

Использование отдельных классов для разных единиц одной и той же меры (например, см, мм и фут для длины) кажется странным. Основываясь на классах DateTime и TimeSpan .NET Framework, я ожидал бы что-то вроде этого:

Length  length       = Length.FromMillimeters(n1);
decimal lengthInFeet = length.Feet;
Length  length2      = length.AddFeet(n2);
Length  length3      = length + Length.FromMeters(n3);
10 голосов
/ 22 июля 2013

Я недавно выпустил Units.NET на GitHub и на NuGet .

Это дает вам все общие единицы и преобразования. Это легкий, испытанный блок и поддерживает PCL.

Пример преобразования:

Length meter = Length.FromMeters(1);
double cm = meter.Centimeters; // 100
double yards = meter.Yards; // 1.09361
double feet = meter.Feet; // 3.28084
double inches = meter.Inches; // 39.3701
10 голосов
/ 03 июля 2012

Теперь такая библиотека C # существует: http://www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

Он имеет почти те же функции, что и проверка времени компиляции модуля F #, но для C #. Ядром является задача MSBuild, которая анализирует код и ищет проверки.

Информация об устройстве хранится в комментариях и атрибутах.

4 голосов
/ 10 февраля 2011

Вот моя забота о создании модулей в C # / VB. Пожалуйста, поправьте меня, если вы думаете, что я неправ. Кажется, что большинство реализаций, о которых я читал, включают создание структуры, которая объединяет значение (целое или двойное) с единицей. Затем вы пытаетесь определить основные функции (+ - * / и т. Д.) Для этих структур, которые принимают во внимание преобразование единиц и согласованность.

Я нахожу эту идею очень привлекательной, но каждый раз, когда я возражаю, какой огромный шаг для проекта это выглядит. Это похоже на сделку "все или ничего". Вы вероятно не просто изменили бы несколько чисел на единицы; Все дело в том, что все данные внутри проекта соответствующим образом помечены единицами, чтобы избежать двусмысленности. Это означает попрощаться с использованием обычных двойных и целых чисел, каждая переменная теперь определяется как «Единица измерения», «Длина» или «Метры» и т. Д. Люди действительно делают это в больших масштабах? Поэтому, даже если у вас большой массив, каждый элемент должен быть помечен единицей. Это, очевидно, будет иметь как размер, так и производительность.

Несмотря на всю хитрость в попытке отодвинуть логику юнита на задний план, некоторые громоздкие обозначения кажутся неизбежными в C #. F # делает некоторую закулисную магию, которая лучше уменьшает фактор раздражения логики юнитов.

Кроме того, насколько успешно мы можем заставить компилятор обрабатывать модуль так же, как обычный double, когда мы этого желаем, без использования CType или ".Value" или каких-либо дополнительных обозначений? Как, например, с nullables, код знает, как обращаться с двойным? точно так же как double (конечно, если ваш double? равен нулю, тогда вы получите ошибку).

3 голосов
/ 16 ноября 2010

Спасибо за идею. Я реализовал модули в C # множеством разных способов, которые всегда кажутся подвохом. Теперь я могу попробовать еще раз, используя идеи, обсужденные выше. Моя цель - иметь возможность определять новые юниты на основе существующих, таких как

Unit lbf = 4.44822162*N;
Unit fps = feet/sec;
Unit hp = 550*lbf*fps

и для программы, чтобы определить правильные размеры, масштабирование и символ для использования. В конце мне нужно построить базовую систему алгебры, которая может преобразовывать такие вещи, как (m/s)*(m*s)=m^2, и пытаться выразить результат на основе существующих определенных единиц.

Также должно быть требование иметь возможность сериализации модулей таким образом, чтобы новые модули не кодировались, а просто объявлялись в XML-файле, например:

<DefinedUnits>
  <DirectUnits>
<!-- Base Units -->
<DirectUnit Symbol="kg"  Scale="1" Dims="(1,0,0,0,0)" />
<DirectUnit Symbol="m"   Scale="1" Dims="(0,1,0,0,0)" />
<DirectUnit Symbol="s"   Scale="1" Dims="(0,0,1,0,0)" />
...
<!-- Derived Units -->
<DirectUnit Symbol="N"   Scale="1" Dims="(1,1,-2,0,0)" />
<DirectUnit Symbol="R"   Scale="1.8" Dims="(0,0,0,0,1)" />
...
  </DirectUnits>
  <IndirectUnits>
<!-- Composite Units -->
<IndirectUnit Symbol="m/s"  Scale="1"     Lhs="m" Op="Divide" Rhs="s"/>
<IndirectUnit Symbol="km/h" Scale="1"     Lhs="km" Op="Divide" Rhs="hr"/>
...
<IndirectUnit Symbol="hp"   Scale="550.0" Lhs="lbf" Op="Multiply" Rhs="fps"/>
  </IndirectUnits>
</DefinedUnits>
1 голос
/ 31 марта 2014

Вы могли бы использовать КоличествоSystem вместо того, чтобы реализовать это самостоятельно. Он основан на F # и значительно улучшает обработку модулей в F #. Это лучшая реализация, которую я нашел до сих пор, и она может быть использована в проектах на C #.

http://quantitysystem.codeplex.com

1 голос
/ 08 декабря 2008

есть jscience: http://jscience.org/,, и вот отличная dsl для юнитов: http://groovy.dzone.com/news/domain-specific-language-unit-. iirc, c # имеет замыкания, так что вы должны быть в состоянии что-то сложить.

0 голосов
/ 27 сентября 2011

Мне очень понравилось читать этот вопрос о переполнении стека и его ответы.

У меня есть любимый проект, с которым я возился на протяжении многих лет, и недавно начал переписывать его и опубликовал в открытом коде на http://ngenericdimensions.codeplex.com

Это похоже на многие идеи, высказанные в вопросе и ответах на этой странице.

В основном речь идет о создании универсальных измерений с единицей измерения и собственным типом данных в качестве заполнителей универсального типа.

Например:

Dim myLength1 as New Length(of Miles, Int16)(123)

С некоторыми дополнительными методами расширения, такими как:

Dim myLength2 = 123.miles

И

Dim myLength3 = myLength1 + myLength2
Dim myArea1 = myLength1 * myLength2

Это не скомпилируется:

Dim myValue = 123.miles + 234.kilograms

Новые модули могут быть расширены в ваших собственных библиотеках.

Эти типы данных являются структурами, которые содержат только 1 внутреннюю переменную-член, что делает их легкими.

По сути, перегрузки операторов ограничены структурами «измерения», поэтому каждая единица измерения не нуждается в перегрузках операторов.

Конечно, большим недостатком является более длинное объявление обобщенного синтаксиса, для которого требуется 3 типа данных. Так что, если это проблема для вас, тогда это не ваша библиотека.

Основная цель состояла в том, чтобы иметь возможность украсить интерфейс модулями в режиме проверки во время компиляции.

С библиотекой нужно многое сделать, но я хотел опубликовать ее на тот случай, если кто-то ищет то.

...