2011-10-21 15 views
6

He estado intentando aprender un poco más sobre delegados y lambdas mientras trabajo en un pequeño proyecto de cocina que involucra conversión de temperatura, así como algunas conversiones de medición de cocina como Imperial to Metric y he estado tratando de encontrar una manera de hacer un convertidor de unidad extensible.¿Cómo creo un convertidor genérico para unidades de medida en C#?

Esto es lo que comencé, junto con los comentarios del código sobre algunos de mis planes. No tengo planes de usarlo como el siguiente, estaba probando algunas características de C#. No lo sé muy bien, tampoco estoy seguro de cómo llevar esto más allá. ¿Alguien tiene alguna sugerencia sobre cómo crear lo que estoy hablando en los comentarios a continuación? Gracias

namespace TemperatureConverter 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      // Fahrenheit to Celsius : [°C] = ([°F] − 32) × 5⁄9 
      var CelsiusResult = Converter.Convert(11M,Converter.FahrenheitToCelsius); 

      // Celsius to Fahrenheit : [°F] = [°C] × 9⁄5 + 32 
      var FahrenheitResult = Converter.Convert(11M, Converter.CelsiusToFahrenheit); 

      Console.WriteLine("Fahrenheit to Celsius : " + CelsiusResult); 
      Console.WriteLine("Celsius to Fahrenheit : " + FahrenheitResult); 
      Console.ReadLine(); 

      // If I wanted to add another unit of temperature i.e. Kelvin 
      // then I would need calculations for Kelvin to Celsius, Celsius to Kelvin, Kelvin to Fahrenheit, Fahrenheit to Kelvin 
      // Celsius to Kelvin : [K] = [°C] + 273.15 
      // Kelvin to Celsius : [°C] = [K] − 273.15 
      // Fahrenheit to Kelvin : [K] = ([°F] + 459.67) × 5⁄9 
      // Kelvin to Fahrenheit : [°F] = [K] × 9⁄5 − 459.67 
      // The plan is to have the converters with a single purpose to convert to 
      //one particular unit type e.g. Celsius and create separate unit converters 
      //that contain a list of calculations that take one specified unit type and then convert to their particular unit type, in this example its Celsius. 
     } 
    } 

    // at the moment this is a static class but I am looking to turn this into an interface or abstract class 
    // so that whatever implements this interface would be supplied with a list of generic deligate conversions 
    // that it can invoke and you can extend by adding more when required. 
    public static class Converter 
    { 
     public static Func<decimal, decimal> CelsiusToFahrenheit = x => (x * (9M/5M)) + 32M; 
     public static Func<decimal, decimal> FahrenheitToCelsius = x => (x - 32M) * (5M/9M); 

     public static decimal Convert(decimal valueToConvert, Func<decimal, decimal> conversion) { 
      return conversion.Invoke(valueToConvert); 
     } 
    } 
} 

Actualización: Tratando de aclarar mi pregunta:

Utilizando sólo mi ejemplo temperatura por debajo, ¿cómo iba a crear una clase que contiene una lista de las conversiones lambda a Celsius que luego pasan una dada temperatura y tratará y convertir que a Celsius (si el cálculo está disponible)

Ejemplo pseudo código:

enum Temperature 
{ 
    Celcius, 
    Fahrenheit, 
    Kelvin 
} 

UnitConverter CelsiusConverter = new UnitConverter(Temperature.Celsius); 
CelsiusConverter.AddCalc("FahrenheitToCelsius", lambda here); 
CelsiusConverter.Convert(Temperature.Fahrenheit, 11); 
+0

unidades de medida se admiten en F # y creo que puede ser una buena [característica] (https://github.com/dotnet/roslyn/issues/144) para C# vNext. Por ahora encontré este proyecto [QuantityTypes] (https://github.com/objorke/QuantityTypes) que implementa Unidades of Measure en C#. – orad

Respuesta

21

Pensé que se trataba de un pequeño problema interesante, así que decidí ver qué bien podría estar incluido en una implementación genérica.Esto no está bien probado (y no maneja todos los casos de error, como por ejemplo, si no registra la conversión para un tipo de unidad en particular, luego la transfiere), pero podría ser útil. El foco estaba en hacer que la clase heredada (TemperatureConverter) fuera lo más ordenada posible.

/// <summary> 
/// Generic conversion class for converting between values of different units. 
/// </summary> 
/// <typeparam name="TUnitType">The type representing the unit type (eg. enum)</typeparam> 
/// <typeparam name="TValueType">The type of value for this unit (float, decimal, int, etc.)</typeparam> 
abstract class UnitConverter<TUnitType, TValueType> 
{ 
    /// <summary> 
    /// The base unit, which all calculations will be expressed in terms of. 
    /// </summary> 
    protected static TUnitType BaseUnit; 

    /// <summary> 
    /// Dictionary of functions to convert from the base unit type into a specific type. 
    /// </summary> 
    static ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>> ConversionsTo = new ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>>(); 

    /// <summary> 
    /// Dictionary of functions to convert from the specified type into the base unit type. 
    /// </summary> 
    static ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>> ConversionsFrom = new ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>>(); 

    /// <summary> 
    /// Converts a value from one unit type to another. 
    /// </summary> 
    /// <param name="value">The value to convert.</param> 
    /// <param name="from">The unit type the provided value is in.</param> 
    /// <param name="to">The unit type to convert the value to.</param> 
    /// <returns>The converted value.</returns> 
    public TValueType Convert(TValueType value, TUnitType from, TUnitType to) 
    { 
     // If both From/To are the same, don't do any work. 
     if (from.Equals(to)) 
      return value; 

     // Convert into the base unit, if required. 
     var valueInBaseUnit = from.Equals(BaseUnit) 
           ? value 
           : ConversionsFrom[from](value); 

     // Convert from the base unit into the requested unit, if required 
     var valueInRequiredUnit = to.Equals(BaseUnit) 
           ? valueInBaseUnit 
           : ConversionsTo[to](valueInBaseUnit); 

     return valueInRequiredUnit; 
    } 

    /// <summary> 
    /// Registers functions for converting to/from a unit. 
    /// </summary> 
    /// <param name="convertToUnit">The type of unit to convert to/from, from the base unit.</param> 
    /// <param name="conversionTo">A function to convert from the base unit.</param> 
    /// <param name="conversionFrom">A function to convert to the base unit.</param> 
    protected static void RegisterConversion(TUnitType convertToUnit, Func<TValueType, TValueType> conversionTo, Func<TValueType, TValueType> conversionFrom) 
    { 
     if (!ConversionsTo.TryAdd(convertToUnit, conversionTo)) 
      throw new ArgumentException("Already exists", "convertToUnit"); 
     if (!ConversionsFrom.TryAdd(convertToUnit, conversionFrom)) 
      throw new ArgumentException("Already exists", "convertToUnit"); 
    } 
} 

Los argumentos de tipo genérico son para una enumeración que representa las unidades y el tipo para el valor. Para usarlo, solo tiene que heredar de esta clase (proporcionando los tipos) y registrar algunas lambdas para hacer la conversión. He aquí un ejemplo para la temperatura (con algunos cálculos ficticios):

enum Temperature 
{ 
    Celcius, 
    Fahrenheit, 
    Kelvin 
} 

class TemperatureConverter : UnitConverter<Temperature, float> 
{ 
    static TemperatureConverter() 
    { 
     BaseUnit = Temperature.Celcius; 
     RegisterConversion(Temperature.Fahrenheit, v => v * 2f, v => v * 0.5f); 
     RegisterConversion(Temperature.Kelvin, v => v * 10f, v => v * 0.05f); 
    } 
} 

Y después de usarlo es bastante simple:

var converter = new TemperatureConverter(); 

Console.WriteLine(converter.Convert(1, Temperature.Celcius, Temperature.Fahrenheit)); 
Console.WriteLine(converter.Convert(1, Temperature.Fahrenheit, Temperature.Celcius)); 

Console.WriteLine(converter.Convert(1, Temperature.Celcius, Temperature.Kelvin)); 
Console.WriteLine(converter.Convert(1, Temperature.Kelvin, Temperature.Celcius)); 

Console.WriteLine(converter.Convert(1, Temperature.Kelvin, Temperature.Fahrenheit)); 
Console.WriteLine(converter.Convert(1, Temperature.Fahrenheit, Temperature.Kelvin)); 
+0

Exactamente lo que buscaba, excelente respuesta :) mucho por leer y entender completamente para ambos, pero muchas gracias por la ayuda. – Pricey

3

Parece que quieren algo como:

Func<decimal, decimal> celsiusToKelvin = x => x + 273.15m; 
Func<decimal, decimal> kelvinToCelsius = x => x - 273.15m; 
Func<decimal, decimal> fahrenheitToKelvin = x => ((x + 459.67m) * 5m)/9m; 
Func<decimal, decimal> kelvinToFahrenheit = x => ((x * 9m)/5m) - 459.67m; 

Sin embargo, es posible que desee considerar no sólo el uso de decimal, pero con un tipo que sabe las unidades por lo que puede no accidental (por ejemplo) aplicar el Conversión de "Celsius a Kelvin" a un valor que no sea Celsius. Posiblemente eche un vistazo al enfoque F# Units of Measure para inspirarse.

+0

Sí, estoy tratando de hacerlo para poder tener una clase que implemente una interfaz Unit Converter y esa clase en particular solo sabría cómo convertir una lista de tipos de temperatura en Celsius, pero luego podría crear una clase completamente nueva que implementa la interfaz del convertidor de la unidad y sabe cómo convertir una medida de taza en cucharadas o imperial en métrica, etc. Intentaré editar mi pregunta para explicarme más a fondo. Gracias de nuevo por el enlace de F # Unidades de medida :) – Pricey

+0

lo siento imperial a métrico es un mal ejemplo en mi comentario anterior, uno mejor sería libras a gramos, que es más específico. – Pricey

5

Tiene un buen comienzo, pero como dijo Jon, actualmente no es seguro; el convertidor no tiene comprobación de errores para garantizar que el decimal que obtiene sea un valor Celsius.

Por lo tanto, para llevar esto más lejos, comenzaría a introducir tipos de estructuras que toman el valor numérico y lo aplican a una unidad de medida. En los Patrones de la Arquitectura Empresarial (también conocido como la Pandilla de los cuatro patrones de diseño), esto se conoce como el patrón "Dinero" después del uso más común, para denotar una cantidad de un tipo de moneda. El patrón se cumple para cualquier cantidad numérica que requiera que una unidad de medida sea significativa.

Ejemplo:

public enum TemperatureScale 
{ 
    Celsius, 
    Fahrenheit, 
    Kelvin 
} 

public struct Temperature 
{ 
    decimal Degrees {get; private set;} 
    TemperatureScale Scale {get; private set;} 

    public Temperature(decimal degrees, TemperatureScale scale) 
    { 
     Degrees = degrees; 
     Scale = scale; 
    } 

    public Temperature(Temperature toCopy) 
    { 
     Degrees = toCopy.Degrees; 
     Scale = toCopy.Scale; 
    } 
} 

Ahora, usted tiene un tipo simple que se puede utilizar para hacer cumplir que las conversiones que están haciendo tienen una temperatura que es de la escala adecuada, y devolver una temperatura resultado conocido para estar en la otra escala

Sus Funcs necesitarán una línea adicional para verificar que la entrada coincida con la salida; se puede seguir utilizando lambdas, o puede tomar un paso más allá con un patrón simple estrategia:

public interface ITemperatureConverter 
{ 
    public Temperature Convert(Temperature input); 
} 

public class FahrenheitToCelsius:ITemperatureConverter 
{ 
    public Temperature Convert(Temperature input) 
    { 
     if (input.Scale != TemperatureScale.Fahrenheit) 
     throw new ArgumentException("Input scale is not Fahrenheit"); 

     return new Temperature(input.Degrees * 5m/9m - 32, TemperatureScale.Celsius); 
    } 
} 

//Implement other conversion methods as ITemperatureConverters 

public class TemperatureConverter 
{ 
    public Dictionary<Tuple<TemperatureScale, TemperatureScale>, ITemperatureConverter> converters = 
     new Dictionary<Tuple<TemperatureScale, TemperatureScale>, ITemperatureConverter> 
     { 
     {Tuple.Create<TemperatureScale.Fahrenheit, TemperatureScale.Celcius>, 
      new FahrenheitToCelsius()}, 
     {Tuple.Create<TemperatureScale.Celsius, TemperatureScale.Fahrenheit>, 
      new CelsiusToFahrenheit()}, 
     ... 
     } 

    public Temperature Convert(Temperature input, TemperatureScale toScale) 
    { 
     if(!converters.ContainsKey(Tuple.Create(input.Scale, toScale)) 
     throw new InvalidOperationException("No converter available for this conversion"); 

     return converters[Tuple.Create(input.Scale, toScale)].Convert(input); 
    } 
} 

Debido a que estos tipos de conversiones son de dos vías, puede considerar la configuración de la interfaz para manejar las dos cosas, con un método "ConvertBack" o similar que tomará una temperatura en la escala Celsius y convertirá a Fahrenheit. Eso reduce tu conteo de clases. Entonces, en lugar de instancias de clase, los valores de su diccionario podrían ser punteros a métodos en instancias de los convertidores. Esto aumenta un tanto la complejidad de configurar el selector de estrategia principal de TemperatureConverter, pero reduce el número de clases de estrategia de conversión que debe definir.

Observe también que la comprobación de errores se realiza en tiempo de ejecución cuando realmente está tratando de realizar la conversión, requiriendo que este código se pruebe minuciosamente en todos los usos para garantizar que siempre sea correcto. Para evitar esto, puede derivar la clase base de temperatura para producir estructuras CelsiusTemperature y FahrenheitTemperature, que simplemente definirían su Scale como un valor constante. Entonces, el ITemperatureConverter podría convertirse en genérico en dos tipos, ambas Temperaturas, lo que le permite comprobar en tiempo de compilación que está especificando la conversión que cree que es. el TemperatureConverter también se puede hacer para encontrar ITemperatureConverters dinámicamente, determinar los tipos entre los que se convertirá y configurar automágicamente el diccionario de convertidores para que nunca tenga que preocuparse por agregar otros nuevos. Esto tiene el costo de aumentar el recuento de clases basado en la temperatura; necesitarás cuatro clases de dominio (una base y tres clases derivadas) en lugar de una. También ralentizará la creación de una clase TemperatureConverter, ya que el código para construir reflexivamente el diccionario del convertidor usará un poco de reflexión.

También podría cambiar las enumeraciones para que las unidades de medida se conviertan en "clases de marcador"; clases vacías que no tienen otro significado que el de ser de esa clase y derivar de otras clases. A continuación, puede definir una jerarquía completa de clases "UnitOfMeasure" que representan las diversas unidades de medida, y se puede utilizar como argumentos y restricciones de tipo genérico; ITemperatureConverter podría ser genérico para dos tipos, ambos con restricciones para ser clases TemperatureScale, y una implementación CelsiusFahrenheitConverter cerraría la interfaz genérica a los tipos CelsiusDegrees y FahrenheitDegrees, ambos derivados de TemperatureScale. Eso le permite exponer las unidades de medida a sí mismas como restricciones de una conversión, lo que a su vez permite conversiones entre tipos de unidades de medida (ciertas unidades de ciertos materiales tienen conversiones conocidas; 1 British Imperial Pint of water pesa 1.25 libras).

Todas estas son decisiones de diseño que simplificarán un tipo de cambio en este diseño, pero a un costo (ya sea haciendo algo más difícil de hacer o disminuyendo el rendimiento del algoritmo). Depende de usted decidir lo que es realmente "fácil" para que, en el contexto de la aplicación general y el entorno de codificación se trabaja en

EDIT:. El uso que usted desee, desde su edición, es extremadamente fácil para la temperatura . Sin embargo, si desea un UnitConverter genérico que pueda funcionar con cualquier UnitofMeasure, ya no desea que los Enums representen sus unidades de medida, porque los Enums no pueden tener una jerarquía de herencia personalizada (derivan directamente de System.Enum).

Puede especificar que el constructor predeterminado pueda aceptar cualquier Enum, pero luego debe asegurarse de que Enum sea uno de los tipos que es una unidad de medida; de lo contrario, podría pasar un valor DialogResult y el convertidor se volvería loco fuera en tiempo de ejecución.

En su lugar, si quiere un UnitConverter que pueda convertir a cualquier UnitOfMeasure dado lambdas para otras unidades de medida, yo especificaría las unidades de medida como "clases de marcador"; pequeñas "testigos" sin estado que sólo tienen sentido en que ellos son su propio tipo y se derivan de sus padres:

//The only functionality any UnitOfMeasure needs is to be semantically equatable 
//with any other reference to the same type. 
public abstract class UnitOfMeasure:IEquatable<UnitOfMeasure> 
{ 
    public override bool Equals(UnitOfMeasure other) 
    { 
     return this.ReferenceEquals(other) 
     || this.GetType().Name == other.GetType().Name; 
    } 

    public override bool Equals(Object other) 
    { 
     return other is UnitOfMeasure && this.Equals(other as UnitOfMeasure); 
    }  

    public override operator ==(Object other) {return this.Equals(other);} 
    public override operator !=(Object other) {return this.Equals(other) == false;} 

} 

public abstract class Temperature:UnitOfMeasure { 
public static CelsiusTemperature Celsius {get{return new CelsiusTemperature();}} 
public static FahrenheitTemperature Fahrenheit {get{return new CelsiusTemperature();}} 
public static KelvinTemperature Kelvin {get{return new CelsiusTemperature();}} 
} 
public class CelsiusTemperature:Temperature{} 
public class FahrenheitTemperature :Temperature{} 
public class KelvinTemperature :Temperature{} 

... 

public class UnitConverter 
{ 
    public UnitOfMeasure BaseUnit {get; private set;} 
    public UnitConverter(UnitOfMeasure baseUnit) {BaseUnit = baseUnit;} 

    private readonly Dictionary<UnitOfMeasure, Func<decimal, decimal>> converters 
     = new Dictionary<UnitOfMeasure, Func<decimal, decimal>>(); 

    public void AddConverter(UnitOfMeasure measure, Func<decimal, decimal> conversion) 
    { converters.Add(measure, conversion); } 

    public void Convert(UnitOfMeasure measure, decimal input) 
    { return converters[measure](input); } 
} 

que puede poner en la comprobación de errores (comprobar que la unidad de entrada tiene una conversión especificada, compruebe que una la conversión que se agrega es para una UOM con el mismo elemento principal que el tipo de base, etc., etc.) como mejor le parezca. También puede derivar UnitConverter para crear TemperatureConverter, lo que le permite agregar comprobaciones estáticas de tipo de tiempo de compilación y evitar las comprobaciones en tiempo de ejecución que UnitConverter debería usar.

+0

+1 por la excelente respuesta y sus comentarios realmente ayudan con los puntos que no consideré – Pricey

0

Normalmente quería añadir esto como un comentario al post de Danny Tuppeny, pero Parece que no puedo agregar esto como comentario.

He mejorado un poco la solución de @Danny Tuppeny. No quería agregar cada conversión con dos factores de conversación, porque solo uno debería ser necesario. Además, el parámetro de tipo Func no parece ser necesario, solo lo hace más complicado para el usuario.

Así que mi llamada se parecería a:

public enum TimeUnit 
{ 
    Milliseconds, 
    Second, 
    Minute, 
    Hour, 
    Day, 
    Week 
} 

public class TimeConverter : UnitConverter<TimeUnit, double> 
{ 
    static TimeConverter() 
    { 
     BaseUnit = TimeUnit.Second; 
     RegisterConversion(TimeUnit.Milliseconds, 1000); 
     RegisterConversion(TimeUnit.Minute, 1/60); 
     RegisterConversion(TimeUnit.Hour, 1/3600); 
     RegisterConversion(TimeUnit.Day, 1/86400); 
     RegisterConversion(TimeUnit.Week, 1/604800); 
    } 
} 

también agregué un método para obtener el factor de conversión entre las unidades. Esta es la clase UnitConverter modificación:

/// <summary> 
/// Generic conversion class for converting between values of different units. 
/// </summary> 
/// <typeparam name="TUnitType">The type representing the unit type (eg. enum)</typeparam> 
/// <typeparam name="TValueType">The type of value for this unit (float, decimal, int, etc.)</typeparam> 
/// <remarks>http://stackoverflow.com/questions/7851448/how-do-i-create-a-generic-converter-for-units-of-measurement-in-c 
/// </remarks> 
public abstract class UnitConverter<TUnitType, TValueType> where TValueType : struct, IComparable, IComparable<TValueType>, IEquatable<TValueType>, IConvertible 
{ 
    /// <summary> 
    /// The base unit, which all calculations will be expressed in terms of. 
    /// </summary> 
    protected static TUnitType BaseUnit; 

    /// <summary> 
    /// Dictionary of functions to convert from the base unit type into a specific type. 
    /// </summary> 
    static ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>> ConversionsTo = new ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>>(); 

    /// <summary> 
    /// Dictionary of functions to convert from the specified type into the base unit type. 
    /// </summary> 
    static ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>> ConversionsFrom = new ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>>(); 

    /// <summary> 
    /// Converts a value from one unit type to another. 
    /// </summary> 
    /// <param name="value">The value to convert.</param> 
    /// <param name="from">The unit type the provided value is in.</param> 
    /// <param name="to">The unit type to convert the value to.</param> 
    /// <returns>The converted value.</returns> 
    public TValueType Convert(TValueType value, TUnitType from, TUnitType to) 
    { 
     // If both From/To are the same, don't do any work. 
     if (from.Equals(to)) 
      return value; 

     // Convert into the base unit, if required. 
     var valueInBaseUnit = from.Equals(BaseUnit) 
           ? value 
           : ConversionsFrom[from](value); 

     // Convert from the base unit into the requested unit, if required 
     var valueInRequiredUnit = to.Equals(BaseUnit) 
           ? valueInBaseUnit 
           : ConversionsTo[to](valueInBaseUnit); 

     return valueInRequiredUnit; 
    } 

    public double ConversionFactor(TUnitType from, TUnitType to) 
    { 
     return Convert(One(), from, to).ToDouble(CultureInfo.InvariantCulture); 
    } 

    /// <summary> 
    /// Registers functions for converting to/from a unit. 
    /// </summary> 
    /// <param name="convertToUnit">The type of unit to convert to/from, from the base unit.</param> 
    /// <param name="conversionToFactor">a factor converting into the base unit.</param> 
    protected static void RegisterConversion(TUnitType convertToUnit, TValueType conversionToFactor) 
    { 
     if (!ConversionsTo.TryAdd(convertToUnit, v=> Multiply(v, conversionToFactor))) 
      throw new ArgumentException("Already exists", "convertToUnit"); 

     if (!ConversionsFrom.TryAdd(convertToUnit, v => MultiplicativeInverse(conversionToFactor))) 
      throw new ArgumentException("Already exists", "convertToUnit"); 
    } 

    static TValueType Multiply(TValueType a, TValueType b) 
    { 
     // declare the parameters 
     ParameterExpression paramA = Expression.Parameter(typeof(TValueType), "a"); 
     ParameterExpression paramB = Expression.Parameter(typeof(TValueType), "b"); 
     // add the parameters together 
     BinaryExpression body = Expression.Multiply(paramA, paramB); 
     // compile it 
     Func<TValueType, TValueType, TValueType> multiply = Expression.Lambda<Func<TValueType, TValueType, TValueType>>(body, paramA, paramB).Compile(); 
     // call it 
     return multiply(a, b); 
    } 

    static TValueType MultiplicativeInverse(TValueType b) 
    { 
     // declare the parameters 
     ParameterExpression paramA = Expression.Parameter(typeof(TValueType), "a"); 
     ParameterExpression paramB = Expression.Parameter(typeof(TValueType), "b"); 
     // add the parameters together 
     BinaryExpression body = Expression.Divide(paramA, paramB); 
     // compile it 
     Func<TValueType, TValueType, TValueType> divide = Expression.Lambda<Func<TValueType, TValueType, TValueType>>(body, paramA, paramB).Compile(); 
     // call it 
     return divide(One(), b); 
    } 

    //Returns the value "1" as converted Type 
    static TValueType One() 
    { 
     return (TValueType) System.Convert.ChangeType(1, typeof (TValueType)); 
    } 
} 
0

uno puede definir una unidades físicas de tipo genérico de tal manera que, si uno tiene para cada unidad de un tipo que implementa new e incluye un método de conversión entre esa unidad y una "unidad de base "de ese tipo, uno puede realizar la aritmética en valores que se expresan en unidades diferentes y hacer que se conviertan según sea necesario, utilizando el sistema de tipo de modo que una variable de tipo AreaUnit<LengthUnit.Inches> solo acepte cosas acotadas en pulgadas cuadradas, pero si se dice myAreaInSquareInches= AreaUnit<LengthUnit.Inches>.Product(someLengthInCentimeters, someLengthInFathoms); traduciría automáticamente esas otras unidades antes de realizar la multiplicación. En realidad, puede funcionar bastante bien cuando se usa la sintaxis de llamada a método, ya que métodos como el método Product<T1,T2>(T1 p1, T2 p2) pueden aceptar parámetros de tipo genérico para sus operandos. Desafortunadamente, no hay forma de hacer que los operadores sean genéricos, ni hay forma de que un tipo como AreaUnit<T> where T:LengthUnitDescriptor defina un medio de conversión hacia o desde algún otro tipo genérico arbitrario AreaUnit<U>. Un AreaUnit<T> podría definir conversiones hacia y desde, por ejemplo, AreaUnit<Angstrom>, pero no hay forma de que al compilador se le diga que el código al que se le da un AreaUnit<Centimeters> and wants AreaUnit` puede convertir pulgadas a angstroms y luego a centímetros.

1

Puede echar un vistazo a Units.NET. Está en GitHub y NuGet. Proporciona la mayoría de las unidades y conversiones, admite el tipado estático y la enumeración de unidades y el análisis/impresión de abreviaturas. Sin embargo, no analiza las expresiones y no puede ampliar las clases existentes de unidades, pero puede ampliarlas con unidades nuevas de terceros.

conversiones Ejemplo:

Length meter = Length.FromMeters(1); 
double cm = meter.Centimeters; // 100 
double feet = meter.Feet; // 3.28084 
Cuestiones relacionadas