IT박스

C #의 측정 단위-거의

itboxs 2020. 12. 24. 23:28
반응형

C #의 측정 단위-거의


F #의 측정 단위 (Units of Measure)에서 영감을 얻었으며 ( 여기 )에서 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);
    }
}

(정적을 사용하여 객체를 인스턴스화하는 것을 피하고 싶었지만 우리 모두 알고 있듯이 인터페이스에서 정적 메서드를 선언수 없습니다 ) 그러면 다음과 같이 할 수 있습니다.

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

분명히 F # 측정 단위와 비교할 때 여기에 틈이 있습니다.

오, 문제는 이것에 대해 어떻게 생각하십니까? 사용할 가치가 있습니까? 다른 사람이 이미 더 잘 했습니까?

이 주제 영역에 관심이있는 사람들을위한 UPDATE , 여기 에 다른 종류의 솔루션을 논의하는 1997 년 논문 링크가 있습니다 (C # 전용이 아님)


차원 분석이 누락되었습니다. 예를 들어 (연결 한 답변에서) 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
    {
    }
}

또한 파생 된 유형에 대해 더 구체적인 연산자를 정의하여 공통 유형에서 호환되는 단위를 확인하지 않을 수 있습니다.


측정 값을 생성하기 위해 숫자 유형에 확장 메소드를 추가 할 수 있습니다. DSL 같은 느낌이 듭니다.

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

It's not really .NET convention and might not be the most discoverable feature, so perhaps you'd add them in a devoted namespace for people who like them, as well as offering more conventional construction methods.


Using separate classes for different units of the same measure (e.g., cm, mm, and ft for Length) seems kind of weird. Based on the .NET Framework's DateTime and TimeSpan classes, I would expect something like this:

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

Now such a C# library exists: http://www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

It has almost the same features as F#'s unit compile time validation, but for C#. The core is a MSBuild task, which parses the code and looking for validations.

The unit information are stored in comments and attributes.


I recently released Units.NET on GitHub and on NuGet.

It gives you all the common units and conversions. It is light-weight, unit tested and supports PCL.

Example conversions:

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

Here's my concern with creating units in C#/VB. Please correct me if you think I'm wrong. Most implementations I've read about seem to involve creating a structure that pieces together a value (int or double) with a unit. Then you try to define basic functions (+-*/,etc) for these structures that take into account unit conversions and consistency.

I find the idea very attractive, but every time I balk at what a huge step for a project this appears to be. It looks like an all-or-nothing deal. You probably wouldn't just change a few numbers into units; the whole point is that all data inside a project is appropriately labeled with a unit to avoid any ambiguity. This means saying goodbye to using ordinary doubles and ints, every variable is now defined as a "Unit" or "Length" or "Meters", etc. Do people really do this on a large scale? So even if you have a large array, every element should be marked with a unit. This will obviously have both size and performance ramifications.

Despite all the cleverness in trying to push the unit logic into the background, some cumbersome notation seems inevitable with C#. F# does some behind-the-scenes magic that better reduces the annoyance factor of the unit logic.

Also, how successfully can we make the compiler treat a unit just like an ordinary double when we so desire, w/o using CType or ".Value" or any additional notation? Such as with nullables, the code knows to treat a double? just like a double (of course if your double? is null then you get an error).


Thanks for the idea. I have implemented units in C# many different ways there always seems to be a catch. Now I can try one more time using the ideas discussed above. My goal is to be able to define new units based on existing ones like

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

and for the program to figure out the proper dimensions, scaling and symbol to use. In the end I need to build a basic algebra system that can convert things like (m/s)*(m*s)=m^2 and try to express the result based on existing units defined.

Also a requirement must be to be able to serialize the units in a way that new units do not need to be coded, but just declared in a XML file like this:

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

there is jscience: http://jscience.org/, and here is a groovy dsl for units: http://groovy.dzone.com/news/domain-specific-language-unit-. iirc, c# has closures, so you should be able to cobble something up.


you could use QuantitySystem instead of implementing it by your own. It builds on F# and drastically improves unit handling in F#. It's the best implementation I found so far and can be used in C# projects.

http://quantitysystem.codeplex.com


Why not use CodeDom to generate all possible permutations of the units automatically? I know it's not the best - but I will definitely work!


See Boo Ometa (which will be available for Boo 1.0): Boo Ometa and Extensible Parsing


I really liked reading through this stack overflow question and its answers.

I have a pet project that I've tinkered with over the years, and have recently started re-writing it and have released it to the open source at http://ngenericdimensions.codeplex.com

It happens to be somewhat similar to many of the ideas expressed in the question and answers of this page.

It basically is about creating generic dimensions, with the unit of measure and the native datatype as the generic type placeholders.

For example:

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

With also some optional use of Extension Methods like:

Dim myLength2 = 123.miles

And

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

This would not compile:

Dim myValue = 123.miles + 234.kilograms

New units can be extended in your own libraries.

These datatypes are structures that contain only 1 internal member variable, making them lightweight.

Basically, the operator overloads are restricted to the "dimension" structures, so that every unit of measure doesn't need operator overloads.

Of course, a big downside is the longer declaration of the generics syntax that requires 3 datatypes. So if that is a problem for you, then this isn't your library.

The main purpose was to be able to decorate an interface with units in a compile-time checking fashion.

There is a lot that needs to be done to the library, but I wanted to post it in case it was the kind of thing someone was looking for.

ReferenceURL : https://stackoverflow.com/questions/348853/units-of-measure-in-c-sharp-almost

반응형