我一直在努力学习更多有关代表和lambdas的知识,同时参与一个涉及温度转换的小型烹饪项目以及一些烹饪测量转换,例如Imperial to Metric,我一直试图想办法制作可扩展的单位转换器。
以下是我的开始,以及我的一些计划的代码评论。我没有计划像下面这样使用它,我只是测试了C#的一些功能我不太了解,我也不确定如何进一步采取这一点。有没有人对如何在下面的评论中创建我正在谈论的内容有任何建议?感谢
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);
}
}
}
更新:试图澄清我的问题:
仅使用下面的温度示例,我将如何创建一个包含lambda转换列表的类,然后将其传递给定温度,然后尝试将其转换为摄氏度(如果计算可用)< / p>
伪代码示例:
enum Temperature
{
Celcius,
Fahrenheit,
Kelvin
}
UnitConverter CelsiusConverter = new UnitConverter(Temperature.Celsius);
CelsiusConverter.AddCalc("FahrenheitToCelsius", lambda here);
CelsiusConverter.Convert(Temperature.Fahrenheit, 11);
答案 0 :(得分:23)
我认为这是一个有趣的小问题,所以我决定看看这可以很好地包含在一个通用的实现中。这没有经过充分测试(并且不处理所有错误情况 - 例如,如果您没有为特定单元类型注册转换,则将其传入),但它可能很有用。重点是让继承的类(TemperatureConverter
)尽可能整洁。
/// <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");
}
}
泛型类型args用于表示单位的枚举,以及值的类型。要使用它,您只需继承此类(提供类型)并注册一些lambdas来进行转换。这是温度的一个例子(有一些虚拟计算):
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);
}
}
然后使用它非常简单:
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));
答案 1 :(得分:5)
答案 2 :(得分:3)
听起来你想要这样的东西:
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;
但是,您可能不仅要考虑使用decimal
,还要考虑使用知道单位的类型,这样您就不会意外地(比方说)将“Celsius应用于开尔文”转换为非摄氏度值。可能会看一下F# Units of Measure的灵感方法。
答案 3 :(得分:1)
你可以看看Units.NET。它位于GitHub和NuGet上。它提供了最常见的单元和转换,支持静态类型和单元枚举以及解析/打印缩写。它虽然没有解析表达式,但您无法扩展现有的单元类,但您可以使用新的第三方单元扩展它。
转换示例:
Length meter = Length.FromMeters(1);
double cm = meter.Centimeters; // 100
double feet = meter.Feet; // 3.28084
答案 4 :(得分:0)
通常我想将此添加为对Danny Tuppeny帖子的评论,但似乎我无法将其添加为评论。
我从@Danny Tuppeny稍微改进了解决方案。我不想用两个会话因素添加每个转换,因为只需要一个。此外,类型Func的参数似乎不是必需的,它只会使用户更加复杂。
所以我的电话会是这样的:
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);
}
}
我还添加了一种方法来获取单位之间的转换因子。 这是修改后的UnitConverter类:
/// <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));
}
}
答案 5 :(得分:0)
可以定义一个物理单位泛型类型,这样,如果每个单元都有一个实现new
并且包含该单元与该类型“基本单元”之间的转换方法的类型,则可以执行算术对以不同单位表示的值并根据需要进行转换,使用类型系统,使AreaUnit<LengthUnit.Inches>
类型的变量只接受以平方英寸为单位的事物,但如果有人说myAreaInSquareInches= AreaUnit<LengthUnit.Inches>.Product(someLengthInCentimeters, someLengthInFathoms);
它在执行乘法之前会自动翻译其他单位。当使用方法调用语法时,它实际上可以很好地工作,因为像Product<T1,T2>(T1 p1, T2 p2)
方法这样的方法可以接受它们的操作数的泛型类型参数。不幸的是,没有办法让运算符变得通用,也没有办法让像AreaUnit<T> where T:LengthUnitDescriptor
这样的类型定义转换到其他任意泛型AreaUnit<U>
或从其他任意泛型AreaUnit<T>
转换的方法。 AreaUnit<Angstrom>
可以定义与之间的转换,例如AreaUnit<Centimeters> and wants
,但是编译器无法告知给出{{1}} AreaUnit`的代码可以将英寸转换为埃,然后转换为厘米。