2008-12-08 20 views
52

Zainspirowany przez Units of Measure in F# i pomimo potwierdzenia (here), że nie można tego zrobić w języku C#, miałem pomysł na drugi dzień, z którym bawiłem się.Jednostki miary w C# - prawie

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 
    } 
} 

Przykład użycia:

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 

Następnym krokiem jest trudny do realizacji konwersje (fragment)

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

(chciałbym uniknąć uruchamianiu obiektów za pomocą statycznego, ale jak wszyscy znamy can't declare a static method in an interface)) Możesz to zrobić:

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

Oczywiście, jest w tym luka, w porównaniu do F # Jednostek miary (pozwolę ci to rozpracować).

Och, pytanie brzmi: co o tym sądzisz? Czy warto go używać? Czy ktoś już zrobił coś lepszego?

UPDATE dla osób zainteresowanych w tym obszarze tematycznym, here jest link do papieru od 1997 omawiania innego rodzaju rozwiązania (nie specjalnie dla C#)

+2

Spójrz na kalkulator Frink i język programowania Frink. –

+2

Zastanawiam się, czy ktoś podszedł do jednostek w C# z atrybutami dla wartości właściwości klasy. – ja72

+0

Frink to bomba dla tego typu problemu. –

Odpowiedz

14

Korzystanie z oddzielnych klas dla różnych jednostek tego samego środka (np. cm, mm i ft dla długości) wydaje się dziwne. Oparty na klasach .NET Framework DateTime i TimeSpan, chciałbym się spodziewać czegoś takiego:

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

To był mój pierwszy instynkt. Wadą tego jest to, że musisz jednoznacznie zdefiniować operatorów, które łączą wszystkie permutacje jednostek. Staje się to o wiele bardziej skomplikowane, gdy zaczniesz łączyć różne jednostki, takie jak Velocity (Length/TimeSpan), w których uzyskasz bardzo dużą liczbę konwersji FromXXX, które musiałbyś obsługiwać. –

39

Brakuje analizy wymiarowej. Na przykład (z odpowiedzią ty powiązany), w F # można to zrobić:

let g = 9.8<m/s^2> 

i wygeneruje nową jednostkę przyspieszenia, pochodzące z liczników i sekundach (można rzeczywiście zrobić to samo w C++ za pomocą szablonów).

W języku C# możliwe jest przeprowadzanie analizy wymiarowej w środowisku wykonawczym, ale zwiększa ona obciążenie i nie daje korzyści sprawdzania podczas kompilacji. O ile mi wiadomo, nie ma sposobu na wykonanie pełnych jednostek czasu kompilacji w języku C#.

To, czy warto to robić, zależy od aplikacji oczywiście, ale w przypadku wielu zastosowań naukowych jest to zdecydowanie dobry pomysł. Nie znam żadnych istniejących bibliotek dla .NET, ale prawdopodobnie istnieją.

Jeśli jesteś zainteresowany tym, jak to zrobić w czasie wykonywania, chodzi o to, że każda wartość ma wartość skalarną i liczby całkowite reprezentujące moc każdej jednostki podstawowej.

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 
      ...; 
    } 
} 

Jeśli nie pozwolić użytkownikowi na zmianę wartości jednostki (dobrym pomysłem tak czy inaczej), można dodać podklasy dla wspólnych zespołów:

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 
    { 
    } 
} 

Można również zdefiniować bardziej szczegółowe operatorzy na typach pochodnych, aby uniknąć sprawdzania kompatybilnych jednostek na typach wspólnych.

+0

Yeh, wiedziałem, że tego właśnie brakowało, bardzo podoba mi się twoje rozwiązanie, ale jak mówisz, nie jest to czas kompilacji. Głosuj mimo to. – Benjol

+0

Nie lubię widzieć inicjalizatorów, którzy rosną w złożoności, gdy dodamy więcej podstawowych jednostek. Ponieważ już tracisz możliwość wykrywania niewłaściwych jednostek w czasie kompilacji, możesz pójść o krok dalej i po prostu użyć słownika odwzorowującego ciąg lub wyliczenie na int, zamiast mieć osobne pole dla każdego typu. – Brian

+0

Istnieje tylko 7 jednostek podstawowych, jeśli weźmiesz system SI (czas, masa, długość, temperatura, natężenie światła, ilość substancji i prąd elektryczny). Jeśli dodasz wartość mnożnika do jednostki, która jest fabryką konwersji z powrotem do reprezentacji SI, możesz uzyskać dość dobry model. –

0

Dlaczego nie używać CodeDom do automatycznego generowania wszystkich możliwych permutacji jednostek? Wiem, że nie jest najlepszy - ale na pewno będę pracował!

+0

Czy proponujesz użycie CodeDom do generowania kodu? –

0

See Boo Ometa (który będzie dostępny dla Boo 1.0): Boo Ometa and Extensible Parsing

+0

Link wydaje się być uszkodzony. –

+0

link http://bamboo.github.com/2008/08/05/boo-ometa-and-extensible-parsing-I.html –

+0

naprawiono, przepraszam, nie widziałem tego wcześniej. –

15

można dodać metody rozszerzenie na typów liczbowych do generowania środków. Byłoby czuć się nieco DSL-odczuwalna:

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

to naprawdę nie jest konwencja .NET i może nie być najbardziej wykrywalne cecha, więc może chcesz dodać je do przestrzeni nazw oddany dla ludzi, którzy je lubią, jak oraz oferowanie bardziej konwencjonalnych metod budowy.

3

Dzięki za pomysł. Zaimplementowałem jednostki w C# na wiele różnych sposobów, zawsze wydaje się, że jest to haczyk. Teraz mogę spróbować jeszcze raz wykorzystując pomysły omówione powyżej. Moim celem jest, aby być w stanie zdefiniować nowe jednostki oparte na istniejących jak

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

i dla programu, aby dowiedzieć się odpowiednie wymiary, skalowanie i symbol do wykorzystania. W końcu muszę zbudować podstawowy system algebry, który może konwertować takie rzeczy jak (m/s)*(m*s)=m^2 i spróbować wyrazić wynik na podstawie zdefiniowanych zdefiniowanych jednostek.

również wymóg musi być zdolny do serializacji jednostki w taki sposób, że nowe jednostki nie muszą być kodowane, ale tylko zadeklarowane w pliku XML jak poniżej:

<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> 
+2

Nie wiem, dlaczego dostałeś awans, ale myślę, że lepiej byłoby nauczyć się F # niż próbować ponownie wymyślić koło. Zaktualizowałem swoje pytanie linkiem do artykułu, który może Cię zainteresować. – Benjol

+0

Dzięki za konstruktywny komentarz. – ja72

3

oto moja troska o tworzenie jednostek w C#/VB. Proszę mnie poprawić, jeśli uważasz, że się mylę. Większość implementacji, o których czytałem, wydaje się obejmować tworzenie struktury, która łączy w sobie wartość (int lub double) z jednostką. Następnie próbujesz zdefiniować podstawowe funkcje (+ - */itp.) Dla tych struktur, które uwzględniają konwersje jednostek i spójność.

Uważam, że pomysł jest bardzo atrakcyjny, ale za każdym razem, gdy zaczynam się zastanawiać, jak wielkim krokiem jest ten projekt, wydaje się. Wygląda na umowę typu "wszystko albo nic". Prawdopodobnie nie zmienisz tylko kilku liczb w jednostki; chodzi o to, że wszystkie dane wewnątrz projektu są odpowiednio oznaczone jednostką, aby uniknąć jakichkolwiek niejednoznaczności. Oznacza to pożegnanie się ze zwykłymi podwójnymi i int, każda zmienna jest teraz definiowana jako "Jednostka" lub "Długość" lub "Metry" itd. Czy ludzie naprawdę robią to na dużą skalę? Więc nawet jeśli masz dużą tablicę, każdy element powinien być oznaczony jednostką. Będzie to oczywiście miało zarówno konsekwencje pod względem wielkości, jak i wydajności.

Mimo całej sprytu w próbach popchnięcia logiki jednostki w tle, uciążliwa notacja wydaje się nieunikniona w C#. F # wykonuje kilka zakulisowych magii, które lepiej redukują czynnik irytacji logiki jednostki.

Ponadto, w jaki sposób z powodzeniem możemy sprawić, że kompilator potraktuje jednostkę tak, jak zwykłe podwójne, gdy tego chcemy, bez użycia CType lub ".Value" lub jakiejkolwiek dodatkowej notacji? Tak jak z pustymi przestrzeniami, kod wie, jak leczyć podwójne? tak jak podwójny (oczywiście, czy twój podwójny?ma wartość null, a następnie pojawia się błąd).

+0

Tak, późniejszy czas na tę dyskusję, ale właśnie dlatego uważam, że powinna to być funkcja języka podstawowego, aby pomóc programistom, a nie bibliotece kodu, tak aby nie było żadnych abstrakcji w samym skompilowanym kodzie. – Jonas

0

Bardzo mi się spodobało czytanie tego pytania przepełnienia stosu i jego odpowiedzi.

Mam projektu pet że mam majstrował z biegiem lat, a niedawno zaczęła ponownie pisanie go i wydali go na open source na http://ngenericdimensions.codeplex.com

Zdarza się nieco podobny do wielu pomysły wyrażone w pytaniu i odpowiedziach na tej stronie.

W gruncie rzeczy chodzi o tworzenie ogólnych wymiarów, z jednostką miary i rodzimym typem danych jako typowym symbolem zastępczym.

Na przykład:

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

Z jakiegoś także opcjonalnego wykorzystania rozszerzeń metod, takich jak:

Dim myLength2 = 123.miles 

I

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

Nie byłoby skompilować:

Dim myValue = 123.miles + 234.kilograms 

Nowe jednostki można rozszerzyć we własnych bibliotekach.

Te typy danych są strukturami, które zawierają tylko 1 wewnętrzną zmienną składową, co czyni je lekkimi.

Zasadniczo przeciążenia operatora są ograniczone do struktur "wymiarowych", tak aby każda jednostka miary nie wymagała przeciążenia operatora.

Oczywiście dużą wadą jest dłuższa deklaracja składni generycznej, która wymaga 3 typów danych. Więc jeśli to jest dla ciebie problem, to nie twoja biblioteka.

Głównym celem było udekorowanie interfejsu za pomocą jednostek w trybie sprawdzania kompilacji.

Jest wiele rzeczy, które należy zrobić w bibliotece, ale chciałem opublikować to na wypadek, gdyby było to coś, czego ktoś szukał.

9

Teraz taka biblioteka C# istnieje: http://www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

To prawie takie same cechy jak jednostki F # 's skompilować sprawdzanie czasu, ale dla C#. Rdzeń to zadanie MSBuild, które analizuje kod i szuka zatwierdzeń.

Informacje o jednostce są przechowywane w komentarzach i atrybutach.

+1

Interesujący wysiłek, ale autor przyznaje, że jest niezadowolony z projektu i sugeruje, aby zacząć od nowa. Podobną bibliotekę można znaleźć tutaj: https://github.com/InitialForce/UnitsNet – kmote

+0

URL został zmieniony na https://github.com/angularsen/UnitsNet – angularsen

8

Niedawno wydałem Units.NET na GitHub i NuGet.

Daje wszystkie typowe jednostki i konwersje. Jest lekki, testowany i obsługuje PCL.

Przykład konwersji:

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 
0

można użyć QuantitySystem zamiast wdrażać go przez własną rękę.Opiera się na F # i drastycznie poprawia obsługę jednostki w F #. To najlepsza implementacja, jaką znalazłem do tej pory i może być używana w projektach C#.

http://quantitysystem.codeplex.com

Powiązane problemy