63 votes

Les unités de mesure en C# - presque

Inspiré par les Unités de Mesure en F#, et malgré l'affirmation (ici) que vous ne pouvais pas le faire en C#, j'ai eu une idée l'autre jour j'ai été jouer avec.

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'utilisation:

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 les conversions (extrait):

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 l'instanciation d'objets à l'aide de statique, mais comme nous le savons tous, vous ne pouvez 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 ce, par rapport à F# Unités de mesure (je vous laisse découvrir).

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

Mise à JOUR pour les personnes intéressées dans ce domaine, ici est un lien vers un article de 1997 à discuter d'un autre type de solution (pas spécialement pour C#)

42voto

Matthew Crumley Points 47284

Il vous manque de l'analyse dimensionnelle. Par exemple (à partir de la réponse que vous avez associé), en F#, vous pouvez faire ceci:

let g = 9.8<m/s^2>

et il va générer une nouvelle unité de l'accélération, dérivé de mètres et secondes (vous pouvez faire la même chose en C++ à l'aide de modèles).

En C#, il est possible de faire de l'analyse dimensionnelle lors de l'exécution, mais il ajoute de la surcharge et de ne pas vous accorder le bénéfice de la compilation de la vérification. Autant que je sache, il n'y a pas moyen de faire le plein au moment de la compilation des unités en C#.

Si cela vaut la peine de le faire dépend de l'application, bien sûr, mais pour de nombreuses applications scientifiques, il est certainement une bonne idée. Je ne sais pas du tout bibliothèques existantes .NET, mais ils existent probablement.

Si vous êtes intéressé par la façon de le faire au moment de l'exécution, l'idée est que chaque valeur est une valeur scalaire et les nombres 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 n'avez pas permettre à l'utilisateur de modifier la valeur des parts (une bonne idée de toute façon), vous pouvez ajouter des sous-classes de parts ordinaires:

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 de façon plus spécifique, les opérateurs sur les types dérivés pour éviter de vérifier la compatibilité des unités sur les types les plus courants.

19voto

Mark Cidade Points 53945

À l'aide des classes séparées pour les différentes unités de la même mesure (p. ex., cm, mm, et pi de Longueur) semble un peu bizarre. Basé sur la .NET Framework est de type DateTime et TimeSpan classes, je m'attends à quelque chose comme ceci:

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

19voto

Drew Noakes Points 69288

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

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

Ce n'est pas vraiment .NET de la convention et pourrait ne pas être le plus identifiable de la fonctionnalité, de sorte que vous aurez peut-être ajouter dans un consacré espace de noms pour les personnes qui, comme eux, ainsi qu'en offrant de plus les méthodes de construction conventionnelles.

15voto

Andreas Larsen Points 2115

J'ai récemment publié Units.NET sur Github et sur NuGet.

Il vous donne toutes les parts ordinaires et conversions. Il est léger, appareil testé et prend en charge PCL.

Exemple 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

11voto

Henning Points 81

Aujourd'hui C# bibliothèque existe: http://www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

Il a presque les mêmes caractéristiques que F#'s de l'unité de temps de compilation de validation, mais pour C#. Le cœur est une tâche MSBuild, qui analyse le code et à la recherche pour les validations.

L'unité de l'information sont stockés dans les commentaires et les attributs.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X