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

前端 未结 6 501
误落风尘
误落风尘 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:00

    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));
        }
    }
    

提交回复
热议问题