How do I create a generic converter for units of measurement in C#?

前端 未结 6 502
误落风尘
误落风尘 2021-01-31 06:48

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

6条回答
  •  执念已碎
    2021-01-31 07:04

    I thought this was an interesting little problem, so I decided to see how nicely this could be wrapped up into a generic implementation. This isn't well-tested (and doesn't handle all error cases - such as if you don't register the conversion for a particular unit type, then pass that in), but it might be useful. The focus was on making the inherited class (TemperatureConverter) as tidy as possible.

    /// 
    /// 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.)
    abstract class UnitConverter
    {
        /// 
        /// 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;
        }
    
        /// 
        /// Registers functions for converting to/from a unit.
        /// 
        /// The type of unit to convert to/from, from the base unit.
        /// A function to convert from the base unit.
        /// A function to convert to the base unit.
        protected static void RegisterConversion(TUnitType convertToUnit, Func conversionTo, Func conversionFrom)
        {
            if (!ConversionsTo.TryAdd(convertToUnit, conversionTo))
                throw new ArgumentException("Already exists", "convertToUnit");
            if (!ConversionsFrom.TryAdd(convertToUnit, conversionFrom))
                throw new ArgumentException("Already exists", "convertToUnit");
        }
    }
    

    The generic type args are for an enum that represents the units, and the type for the value. To use it, you just have to inherit from this class (providing the types) and register some lambdas to do the conversion. Here's an example for temperature (with some dummy calculations):

    enum Temperature
    {
        Celcius,
        Fahrenheit,
        Kelvin
    }
    
    class TemperatureConverter : UnitConverter
    {
        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);
        }
    }
    

    And then using it is pretty 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));
    

提交回复
热议问题