Подтвердить что ты не робот

Единицы измерения в С# - почти

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

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 # (я позволю вам разобраться).

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

UPDATE для людей, интересующихся этой предметной областью, здесь - это ссылка на документ от 1997 года, в котором обсуждается другой вид решения (не специально для С#)

4b9b3361

Ответ 1

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

let g = 9.8<m/s^2>

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

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

Стоит ли это делать, конечно, зависит от приложения, но для многих научных приложений это определенно хорошая идея. Я не знаю каких-либо существующих библиотек для .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
    {
    }
}

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

Ответ 2

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

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

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

Ответ 3

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

Length  length       = Length.FromMillimeters(n1);
decimal lengthInFeet = length.Feet;
Length  length2      = length.AddFeet(n2);
Length  length3      = length + Length.FromMeters(n3);

Ответ 4

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

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

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

Ответ 5

Недавно я выпустил 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

Ответ 6

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

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>

Ответ 7

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

Я считаю эту идею очень привлекательной, но каждый раз, когда я отказываюсь от какого-то огромного шага для проекта, это кажется. Это похоже на сделку "все или ничего". Вы, вероятно, не просто изменили бы несколько чисел на единицы; все дело в том, что все данные внутри проекта соответствующим образом помечены единицей, чтобы избежать какой-либо двусмысленности. Это означает попрощаться с обычными двойниками и ints, каждая переменная теперь определяется как "единица" или "длина" или "метры" и т.д. Неужели люди действительно делают это в больших масштабах? Поэтому, даже если у вас большой массив, каждый элемент должен быть отмечен блоком. Очевидно, что это будет иметь как размеры, так и последствия.

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

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

Ответ 9

Почему бы не использовать CodeDom для генерации всех возможных перестановок единиц автоматически? Я знаю, что это не лучшее, но я обязательно сработаю!

Ответ 11

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

У меня есть проект для домашних животных, с которым я возился с годами, и недавно начал переписывать его и выпустил его в открытый исходный код на 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

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

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

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

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

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

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

Ответ 12

вы можете использовать QuantitySystem вместо ее реализации самостоятельно. Он основывается на F # и значительно улучшает управление блоками в F #. Это лучшая реализация, которую я нашел до сих пор, и ее можно использовать в проектах С#.

http://quantitysystem.codeplex.com