63

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 を使用してオブジェクトをインスタンス化することは避けたかったのですが、ご存知のように、インターフェイスで 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# 向けではありません)。

4

14 に答える 14

42

ディメンション分析がありません。たとえば(リンク先の回答から)、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
    {
    }
}

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

于 2008-12-08T08:03:59.930 に答える
19

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

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

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

于 2010-06-01T12:43:19.390 に答える
18

同じ尺度の異なる単位 (たとえば、長さの 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);
于 2008-12-08T07:46:23.243 に答える
11

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

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

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

于 2012-07-03T15:05:53.573 に答える
4

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

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

ユニット ロジックをバックグラウンドにプッシュしようとするあらゆる巧妙さにもかかわらず、C# では面倒な表記法が避けられないようです。F# は、ユニット ロジックの煩わしさ要因をより適切に軽減するいくつかの舞台裏の魔法を行います。

また、CType、「.Value」、または追加の表記を使用せずに、必要に応じて、コンパイラにユニットを通常の double のように処理させるにはどうすればよいでしょうか? nullable の場合と同様に、コードは double を扱うことを知っていますか? double と同じです (もちろん double? が null の場合はエラーになります)。

于 2011-02-09T21:54:20.620 に答える
3

アイデアをありがとう。私は 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>
于 2010-11-16T20:13:29.177 に答える
1

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

于 2008-12-08T08:31:54.087 に答える
1

jscience があります: http://jscience.org/、ユニット用の groovy DSL は次のとおりです: http://groovy.dzone.com/news/domain-specific-language-unit-。iirc、c#にはクロージャーがあるので、何かを石畳にすることができるはずです。

于 2008-12-08T07:55:29.637 に答える
1

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

http://quantitysystem.codeplex.com

于 2014-03-30T20:21:21.303 に答える
0

Boo Ometa(Boo 1.0で使用可能になります): BooOmetaとExtensibleParsingを参照してください。

于 2009-01-28T22:07:18.317 に答える
0

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

私は何年にもわたっていじくり回してきたペット プロジェクトを持っており、最近それを書き直し、https://github.com/MafuJosh/NGenericDimensionsでオープン ソースにリリースしました。

このページの質問と回答で表現されているアイデアの多くと多少似ています。

基本的には、測定単位とネイティブ データ型をジェネリック タイプのプレースホルダーとして使用して、ジェネリック ディメンションを作成することです。

例えば:

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 つのデータ型を必要とするジェネリック構文の宣言が長くなることです。それがあなたにとって問題であるなら、これはあなたのライブラリではありません。

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

ライブラリにはやらなければならないことがたくさんありますが、誰かが探しているようなものだった場合に備えて投稿したかったのです。

于 2011-09-27T04:05:27.397 に答える