web-dev-qa-db-ja.com

C#の測定単位-ほとんど

F#の測定単位 に触発され、C ---ではそれができないと( 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を使用してオブジェクトをインスタンス化しないようにしたいと思いますが、誰もが知っているように インターフェイスで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年の論文(特にC#向けではない)

57
Benjol

次元分析がありません。たとえば、(あなたがリンクした回答から)、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
    {
    }
}

派生型でより具体的な演算子を定義して、一般的な型で互換性のあるユニットをチェックしないようにすることもできます。

41
Matthew Crumley

数値型に拡張メソッドを追加して、メジャーを生成できます。それは少しDSLのように感じるでしょう:

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

これは実際には.NETの慣習ではなく、最も発見しやすい機能ではない可能性があるため、おそらくそれらを好きな人のために専用の名前空間に追加し、さらに従来の構築方法を提供します。

18
Drew Noakes

同じメジャーの異なる単位(たとえば、長さのcm、mm、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);
17
Mark Cidade

最近、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
angularsen

現在、このようなC#ライブラリが存在します。 http://www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

F#のユニットコンパイル時検証とほぼ同じ機能を備えていますが、C#向けです。コアはMSBuildタスクで、コードを解析して検証を探します。

ユニット情報はコメントと属性に格納されます。

10
Henning

C#/ VBでユニットを作成する際の私の懸念事項を次に示します。私が間違っていると思ったら私を訂正してください。私がこれまでに読んだほとんどの実装は、ユニットと値(intまたはdouble)をつなぎ合わせる構造の作成を伴うようです。次に、単位変換と一貫性を考慮したこれらの構造の基本関数(+-* /など)を定義しようとします。

このアイデアは非常に魅力的だと思いますが、プロジェクトの大きな一歩を踏み出すたびに、これはどのように思えるかと思います。それはオールオアナッシングの取引のように見えます。おそらく、いくつかの数値を単位に変更するだけではありません。要点は、プロジェクト内のすべてのデータは、あいまいさを回避するために適切にユニットでラベル付けされるということです。これは、通常のdoubleとintを使用するのではなく、すべての変数が「Unit」、「Length」、または「Meters」などとして定義されていることを意味します。人々は本当にこれを大規模に行っていますか?したがって、配列が大きい場合でも、すべての要素に単位を付ける必要があります。これには明らかにサイズとパフォーマンスの両方の影響があります。

ユニットロジックをバックグラウンドにプッシュしようとするすべての賢さにもかかわらず、C#ではいくつかの扱いにくい表記が避けられないようです。 F#は、舞台裏の魔法を使って、ユニットロジックの煩わしさを軽減します。

また、CTypeや ".Value"やその他の表記を使用せずに、コンパイラーが望んでいるときに、ユニットを通常のdoubleのようにどのようにうまく処理できるでしょうか。 nullableの場合など、コードはdoubleを処理することを知っていますか? doubleと同じです(もちろん、double?がnullの場合、エラーが発生します)。

4
Scott

アイデアをありがとう。私は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>
3
ja72

独自に実装する代わりに、QuantitySystemを使用できます。 F#に基づいて構築され、F#でのユニット処理を大幅に改善します。これは私がこれまでに見つけた最良の実装であり、C#プロジェクトで使用できます。

http://quantitysystem.codeplex.com

1
MovGP0

jscienceがあります: http://jscience.org/ 、そしてユニットのgroovy dslがあります: http://groovy.dzone.com/news/domain-specific-language -unit- 。 iirc、c#にはクロージャーがあるので、何かを考え出すことができるはずです。

1
Ray Tayek

私はこのスタックオーバーフローの質問とその回答を読むのが本当に好きでした。

私は長年にわたっていじくり回してきたペットプロジェクトがあり、最近それを書き直し、オープンソースにリリースしました 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つのデータ型を必要とするジェネリック構文の宣言が長くなることです。したがって、それが問題である場合、これはライブラリではありません。

主な目的は、ユニットとのインターフェースをコンパイル時チェック方式で装飾できるようにすることでした。

図書館にやらなければならないことがたくさんありますが、誰かが探していたようなものだったので、それを投稿したかったのです。

0
Mafu Josh

Boo Ometa(Boo 1.0で使用可能になる予定)を参照してください。 Boo OmetaおよびExtensible Parsing

0
justin.m.chase

CodeDomを使用して、ユニットの可能なすべての順列を自動的に生成しませんか?私はそれが最高ではないことを知っています-しかし私は間違いなく働きます!