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