I have been trying to learn a bit more about delegates and lambdas while working on a small cooking project that involves temperature conversion as well as some cooking measurem
Normally I wanted to add this as a comment to Danny Tuppeny's post, but it seems that I am not able to add this as comment.
I improved the solution from @Danny Tuppeny a little bit. I did not want to add each conversion with two conversation factors, because only one should be necessary. Also the parameter of type Func does not seems to be necessary, it only makes it more complicated for the user.
So my call would looks like:
public enum TimeUnit
{
Milliseconds,
Second,
Minute,
Hour,
Day,
Week
}
public class TimeConverter : UnitConverter
{
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);
}
}
I also added a method to get the conversion factor between to units. This is the modified UnitConverter class:
///
/// Generic conversion class for converting between values of different units.
///
/// The type representing the unit type (eg. enum)
/// The type of value for this unit (float, decimal, int, etc.)
/// http://stackoverflow.com/questions/7851448/how-do-i-create-a-generic-converter-for-units-of-measurement-in-c
///
public abstract class UnitConverter where TValueType : struct, IComparable, IComparable, IEquatable, IConvertible
{
///
/// The base unit, which all calculations will be expressed in terms of.
///
protected static TUnitType BaseUnit;
///
/// Dictionary of functions to convert from the base unit type into a specific type.
///
static ConcurrentDictionary> ConversionsTo = new ConcurrentDictionary>();
///
/// Dictionary of functions to convert from the specified type into the base unit type.
///
static ConcurrentDictionary> ConversionsFrom = new ConcurrentDictionary>();
///
/// Converts a value from one unit type to another.
///
/// The value to convert.
/// The unit type the provided value is in.
/// The unit type to convert the value to.
/// The converted value.
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);
}
///
/// Registers functions for converting to/from a unit.
///
/// The type of unit to convert to/from, from the base unit.
/// a factor converting into the base unit.
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 multiply = Expression.Lambda>(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 divide = Expression.Lambda>(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));
}
}