tabla - Unidades de medida en C#- casi
sistemas de medidas en fisica (12)
Inspirado por Unidades de medida en F # , ya pesar de afirmar ( aquí ) que no podías hacerlo en C #, el otro día tuve una idea con la que estuve jugando.
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
}
}
Ejemplo de uso:
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
El siguiente paso es tratar de implementar conversiones (fragmento):
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);
}
}
(Me hubiera gustado evitar la creación de instancias de objetos mediante el uso de estática, pero como todos sabemos que no se puede declarar un método estático en una interfaz ), puede hacer esto:
var e = Length.Convert<Length.mm, Length.m>(c);
var f = Length.Convert<Length.mm, Mass.kg>(d); <-- but not this
Obviamente, hay un gran vacío en esto, en comparación con F # Unidades de medida (te dejaré resolverlo).
Oh, la pregunta es: ¿qué piensas de esto? ¿Vale la pena usar? ¿Alguien más ya lo hizo mejor?
ACTUALIZACIÓN para las personas interesadas en esta área temática, aquí hay un enlace a un documento de 1997 sobre un tipo diferente de solución (no específicamente para C #)
¿Por qué no utilizar CodeDom para generar todas las permutaciones posibles de las unidades de forma automática? Sé que no es el mejor, ¡pero definitivamente voy a trabajar!
Usar clases separadas para diferentes unidades de la misma medida (p. Ej., Cm, mm y ft para Longitud) parece un poco extraño. En base a las clases DateTime y TimeSpan de .NET Framework, esperaría algo como esto:
Length length = Length.FromMillimeters(n1);
decimal lengthInFeet = length.Feet;
Length length2 = length.AddFeet(n2);
Length length3 = length + Length.FromMeters(n3);
hay jscience: http://jscience.org/ , y aquí hay un dsl maravilloso para las unidades: http://groovy.dzone.com/news/domain-specific-language-unit- . iirc, c # tiene cierres, por lo que debería poder improvisar algo.
Puede agregar métodos de extensión en tipos numéricos para generar medidas. Se sentiría un poco como DSL:
var mass = 1.Kilogram();
var length = (1.2).Kilometres();
En realidad, no es la convención .NET y puede que no sea la característica más reconocible, por lo que quizás los agregue en un espacio de nombres dedicado para las personas que les gusta, además de ofrecer métodos de construcción más convencionales.
Gracias por la idea He implementado unidades en C # de muchas maneras diferentes, siempre parece haber una trampa. Ahora puedo intentarlo una vez más usando las ideas discutidas anteriormente. Mi objetivo es poder definir nuevas unidades basadas en las existentes, como
Unit lbf = 4.44822162*N;
Unit fps = feet/sec;
Unit hp = 550*lbf*fps
y para que el programa averigüe las dimensiones, escalas y símbolos adecuados para usar. Al final, necesito construir un sistema álgebra básico que pueda convertir cosas como (m/s)*(m*s)=m^2
e intente expresar el resultado según las unidades existentes definidas.
También se debe tener el requisito de poder serializar las unidades de forma que las nuevas unidades no necesiten codificarse, sino simplemente declararlas en un archivo XML como este:
<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>
Aquí está mi preocupación con la creación de unidades en C # / VB. Por favor corrígeme si crees que estoy equivocado. La mayoría de las implementaciones que he leído parecen implicar la creación de una estructura que combina un valor (int o doble) con una unidad. Luego intenta definir funciones básicas (+ - * /, etc.) para estas estructuras que tienen en cuenta las conversiones de unidad y la coherencia.
Encuentro la idea muy atractiva, pero cada vez que me niego a ver qué gran paso para un proyecto parece ser. Parece un trato de todo o nada. Probablemente no solo cambiarías algunos números en unidades; el punto es que todos los datos dentro de un proyecto están apropiadamente etiquetados con una unidad para evitar cualquier ambigüedad. Esto significa decir adiós al uso de dobles y enteros comunes, cada variable ahora se define como una "Unidad" o "Longitud" o "Medidores", etc. ¿Las personas realmente lo hacen a gran escala? Entonces, incluso si tiene una matriz grande, cada elemento debe estar marcado con una unidad. Obviamente, esto tendrá ramificaciones de tamaño y rendimiento.
A pesar de toda la astucia al tratar de empujar la lógica de la unidad a un segundo plano, algunas notación engorrosas parecen inevitables con C #. F # hace algo de magia entre bastidores que reduce mejor el factor de molestia de la lógica de la unidad.
Además, ¿con qué grado de éxito podemos hacer que el compilador trate una unidad como un doble ordinario cuando lo deseamos, sin usar CType o ".Value" o ninguna notación adicional? Tal como con nulables, ¿el código sabe tratar a un doble? al igual que un doble (por supuesto, si su doble? es nulo, entonces se obtiene un error).
Te estás perdiendo el análisis dimensional. Por ejemplo (de la respuesta a la que vinculó), en F # puede hacer esto:
let g = 9.8<m/s^2>
y generará una nueva unidad de aceleración, derivada de metros y segundos (en realidad, puedes hacer lo mismo en C ++ usando plantillas).
En C #, es posible realizar análisis dimensionales en tiempo de ejecución, pero agrega sobrecarga y no le da el beneficio de la verificación en tiempo de compilación. Por lo que sé, no hay forma de hacer unidades completas en tiempo de compilación en C #.
Si vale la pena hacerlo depende de la aplicación del curso, pero para muchas aplicaciones científicas, definitivamente es una buena idea. No conozco ninguna biblioteca existente para .NET, pero probablemente existan.
Si está interesado en cómo hacerlo en tiempo de ejecución, la idea es que cada valor tenga un valor escalar y números enteros que representen la potencia de cada unidad básica.
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 no permite que el usuario cambie el valor de las unidades (una buena idea), podría agregar subclases para unidades comunes:
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
{
}
}
También podría definir operadores más específicos en los tipos derivados para evitar la búsqueda de unidades compatibles en tipos comunes.
Ver Boo Ometa (que estará disponible para Boo 1.0): Boo Ometa y análisis extensible
Ahora existe una biblioteca de C #: http://www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp
Tiene casi las mismas características que la validación de tiempo de compilación de la unidad F #, pero para C #. El núcleo es una tarea de MSBuild, que analiza el código y busca validaciones.
La información de la unidad se almacena en comentarios y atributos.
Realmente me gustó leer esta pregunta sobre el desbordamiento de pila y sus respuestas.
Tengo un proyecto favorito con el que he trabajado a lo largo de los años, y recientemente comencé a reescribirlo y lo he lanzado al código abierto en http://ngenericdimensions.codeplex.com
Resulta algo similar a muchas de las ideas expresadas en las preguntas y respuestas de esta página.
Básicamente se trata de crear dimensiones genéricas, con la unidad de medida y el tipo de datos nativo como marcadores de posición de tipo genérico.
Por ejemplo:
Dim myLength1 as New Length(of Miles, Int16)(123)
También con algún uso opcional de métodos de extensión como:
Dim myLength2 = 123.miles
Y
Dim myLength3 = myLength1 + myLength2
Dim myArea1 = myLength1 * myLength2
Esto no compilaría:
Dim myValue = 123.miles + 234.kilograms
Las nuevas unidades se pueden extender en sus propias bibliotecas.
Estos tipos de datos son estructuras que contienen solo una variable miembro interna, lo que las hace livianas.
Básicamente, las sobrecargas del operador están restringidas a las estructuras de "dimensión", por lo que cada unidad de medida no necesita sobrecargas del operador.
Por supuesto, un gran inconveniente es la declaración más larga de la sintaxis de los genéricos que requiere 3 tipos de datos. Entonces, si eso es un problema para usted, entonces esta no es su biblioteca.
El objetivo principal era poder decorar una interfaz con unidades en una forma de comprobación en tiempo de compilación.
Hay mucho que hacer en la biblioteca, pero quería publicarlo en caso de que fuera el tipo de cosa que alguien estaba buscando.
puede usar QuantitySystem en lugar de implementarlo por su cuenta. Se basa en F # y mejora drásticamente el manejo de la unidad en F #. Es la mejor implementación que he encontrado hasta ahora y puede usarse en proyectos de C #.
Recientemente lancé Units.NET en GitHub y en NuGet .
Te da todas las unidades y conversiones comunes. Es liviano, probado en unidades y es compatible con PCL.
Conversiones de ejemplo:
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