Les unités de mesure en C# - presque

inspiré par Unités de mesure en F# , et malgré l'affirmation ( ici ) que vous ne pouviez pas le faire en C#, j'ai eu une idée l'autre jour avec laquelle j'ai joué.

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

exemple d'usage:

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

la prochaine étape est d'essayer de mettre en œuvre des conversions (snippet):

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

(j'aurais aimé éviter d'instancier des objets en utilisant des, mais comme nous le savons tous, vous ne peut pas déclarer une méthode statique dans une interface ) Vous pouvez alors faire ceci:

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

évidemment, il y a un trou béant dans ceci, comparé à F# Unités de mesure (je vais vous laisser le régler).

Oh, la question Est: qu'est-ce que vous en pensez? Est-il utile? A quelqu'un d'autre est déjà fait mieux?

mise à jour pour les personnes intéressées par ce sujet la région, ici est un lien vers un article de 1997 à discuter d'un autre type de solution (pas spécialement pour C#)

53
demandé sur Community 2008-12-08 10:32:54

12 réponses

vous manquez l'analyse dimensionnelle. Par exemple (à partir de la réponse à laquelle vous avez lié), en F# vous pouvez faire ceci:

let g = 9.8<m/s^2>

et il générera une nouvelle unité d'accélération, dérivée des mètres et des secondes (vous pouvez en fait faire la même chose en C++ en utilisant des gabarits).

en C#, il est possible de faire une analyse dimensionnelle à l'exécution, mais cela ajoute des frais généraux et ne vous donne pas l'avantage de la vérification du temps de compilation. Autant que je sache, il n'y a aucun moyen de faire des unités à temps plein de compilation dans C#.

si cela vaut la peine de faire dépend de l'application bien sûr, mais pour de nombreuses applications scientifiques, c'est certainement une bonne idée. Je ne connais pas de bibliothèques existantes pour .NET, mais elles existent probablement.

si vous êtes intéressé par la façon de le faire à l'exécution, l'idée est que chaque valeur a une valeur scalaire et des entiers représentant la puissance de chaque unité de base.

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

Si vous ne permettez pas à l'utilisateur de changer la valeur des unités( une bonne idée de toute façon), vous pouvez ajouter des sous-classes pour les unités communes:

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

vous pouvez également définir des opérateurs plus spécifiques sur les types dérivés pour éviter de vérifier les unités compatibles sur les types communs.

39
répondu Matthew Crumley 2011-02-10 04:25:31

utilisant des classes séparées pour différentes unités de la même mesure (par exemple, cm, mm, et ft pour la longueur) semble un peu bizarre. Basé sur les classes DateTime et TimeSpan du Framework. net, je m'attendrais à quelque chose comme ceci:

Length  length       = Length.FromMillimeters(n1);
decimal lengthInFeet = length.Feet;
Length  length2      = length.AddFeet(n2);
Length  length3      = length + Length.FromMeters(n3);
15
répondu Mark Cidade 2008-12-08 07:46:23

vous pouvez ajouter des méthodes d'extension sur les types numériques pour générer des mesures. On dirait un peu DSL:

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

ce n'est pas vraiment .net convention et pourrait ne pas être la caractéristique la plus découvrable, donc peut-être que vous pourriez les ajouter dans un espace de nom dédié pour les gens qui les aiment, en plus d'offrir des méthodes de construction plus conventionnelles.

15
répondu Drew Noakes 2010-06-01 12:43:19

maintenant une telle bibliothèque existe: http://www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

il a presque les mêmes caractéristiques que l'Unité de f#pour la validation de temps de compilation, mais pour C#. Le noyau est une tâche MSBuild, qui analyse le code et recherche des validations.

les informations relatives à l'unité sont stockées dans les commentaires et les attributs.

9
répondu Henning 2012-07-03 15:05:53

j'ai récemment publié Units.NET sur GitHub et sur NuGet .

il vous donne toutes les unités communes et conversions. Il est léger, testé à l'unité et prend en charge PCL.

exemples de 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
9
répondu angularsen 2017-09-30 13:19:51

Merci pour l'idée. J'ai mis en place des unités en C# de nombreuses façons différentes il semble toujours y avoir un hic. Maintenant, je peux essayer une fois de plus en utilisant les idées discutées ci-dessus. Mon but est de pouvoir définir de nouvelles unités basées sur des unités existantes comme

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

et pour que le programme détermine les dimensions, l'échelle et le symbole appropriés à utiliser. En fin de Compte, j'ai besoin de construire un système d'algèbre de base qui peut convertir des choses comme (m/s)*(m*s)=m^2 et d'essayer d'exprimer la résultat basé sur les unités existantes définies.

aussi une exigence doit être d'être en mesure de sérialiser les unités d'une manière que les nouvelles unités n'ont pas besoin d'être codées, mais juste déclaré dans un fichier XML comme ceci:

<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
répondu ja72 2010-11-16 20:13:29

Voici mon problème avec la création d'unités en C# / VB. S'il vous plaît, corrigez-moi si vous pensez que j'ai tort. La plupart des implémentations que j'ai lues semblent impliquer la création d'une structure qui fusionne une valeur (int ou double) avec une unité. Ensuite,vous essayez de définir des fonctions de base (+- */, etc) pour ces structures qui tiennent compte des conversions d'unités et de la cohérence.

je trouve l'idée très attrayante, mais chaque fois que je rechigne à quel pas énorme pour un projet cela apparaît être. On dirait un marché à tout ou rien. Vous ne feriez probablement pas que changer quelques nombres en unités; le point entier est que toutes les données à l'intérieur d'un projet sont étiquetés correctement avec une unité pour éviter toute ambiguïté. Cela signifie Dire Adieu à l'utilisation des doubles et des ints ordinaires, chaque variable est maintenant définie comme une "unité" ou" longueur "ou" mètres", etc. Les gens font-ils vraiment cela à grande échelle? Donc, même si vous avez un grand tableau, chaque élément doit être marqué d'une unité. Ce sera bien évidemment ont à la fois des ramifications de taille et de performance.

malgré toute l'habileté à essayer de faire passer la logique de l'unité à l'arrière-plan, une notation encombrante semble inévitable avec C#. F # fait un peu de magie dans les coulisses qui réduit mieux le facteur de gêne de la logique de l'unité.

aussi, comment faire avec succès le compilateur traiter une unité comme un double ordinaire quand nous le désirons, w/o en utilisant CType ou".Valeur" ou toute autre mention supplémentaire? Comme avec les nullables, le code sait traiter un double? juste comme un double (bien sûr, si votre double? est nul alors vous obtenez une erreur).

3
répondu Scott 2011-02-09 21:54:20

il y a jscience: http://jscience.org / , et voici un groovy dsl pour les unités: http://groovy.dzone.com/news/domain-specific-language-unit - . iirc, c# a des fermetures, donc vous devriez être capable de brosser quelque chose.

1
répondu Ray Tayek 2008-12-08 07:55:29

pourquoi ne pas utiliser CodeDom pour générer toutes les permutations possibles des unités automatiquement? Je sais que c'est pas le meilleur mais je vais certainement travailler!

0
répondu Jonathan C Dickinson 2008-12-08 08:31:54

voir Boo Ometa (qui sera disponible pour Boo 1.0): Boo Ometa et Parsing Extensible

0
répondu justin.m.chase 2011-09-21 14:56:23

j'ai vraiment aimé lire à travers cette question de débordement de pile et ses réponses.

j'ai un projet pour animaux de compagnie que j'ai bricolé au fil des années, et j'ai récemment commencé à le réécrire et je l'ai publié à l'open source à http://ngenericdimensions.codeplex.com

il se trouve que cela ressemble un peu à beaucoup des idées exprimées dans la question et les réponses de cette page.

It il s'agit essentiellement de créer des dimensions génériques, avec l'Unité de mesure et le type de données natif comme le type générique placeholders.

par exemple:

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

avec également une utilisation facultative de méthodes D'Extension comme:

Dim myLength2 = 123.miles

et

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

cela ne compilerait pas:

Dim myValue = 123.miles + 234.kilograms

de nouvelles unités peuvent être étendues dans vos propres bibliothèques.

Ces types de données sont des structures qui ne contiennent qu'une seule variable membre interne, ce qui les rend légers.

essentiellement, les surcharges de l'opérateur sont limitées aux structures "dimensionnelles", de sorte que chaque unité de mesure n'a pas besoin de surcharges de l'opérateur.

bien sûr, un gros inconvénient est la déclaration plus longue de la syntaxe générique qui nécessite 3 types de données. Donc, si c'est un problème pour vous, alors ce n'est pas votre bibliothèque.

le le but principal était d'être capable de décorer une interface avec des unités d'une manière de vérification de compilation.

il y a beaucoup à faire à la bibliothèque, mais je voulais la poster au cas où c'était le genre de chose que quelqu'un cherchait.

0
répondu Mafu Josh 2012-07-03 16:03:54

vous pouvez utiliser QuantitySystem au lieu de l'implémenter vous-même. Il s'appuie sur F# et améliore radicalement la gestion des unités en F#. C'est la meilleure implémentation que j'ai trouvée jusqu'à présent et qui peut être utilisée dans les projets C#.

http://quantitysystem.codeplex.com

0
répondu MovGP0 2014-03-30 20:21:21