// Licensed under MIT No Attribution, see LICENSE file at the root. // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. using System.Collections.Concurrent; using UnitsNet.InternalHelpers; namespace UnitsNet { using ConversionFunctionLookupKey = ValueTuple; /// /// /// /// /// public delegate IQuantity ConversionFunction(IQuantity inputValue); /// /// /// /// /// /// public delegate TQuantity ConversionFunction(TQuantity inputValue) where TQuantity : IQuantity; /// /// Convert between units of a quantity, such as converting from meters to centimeters of a given length. /// public sealed class UnitConverter { /// /// The default singleton instance for converting values from one unit to another.
/// Modify this to add/remove conversion functions at runtime, such as adding your own third-party units and quantities to convert between. ///
/// /// Convenience shortcut for ... /// public static UnitConverter Default => UnitsNetSetup.Default.UnitConverter; /// /// Creates a new instance. /// public UnitConverter() { ConversionFunctions = new ConcurrentDictionary(); } /// /// Creates a new instance with the copied from . /// /// The to copy from. public UnitConverter(UnitConverter other) { ConversionFunctions = new ConcurrentDictionary(other.ConversionFunctions); } /// /// Create an instance of the unit converter with all the built-in unit conversions defined in the library. /// /// The unit converter. public static UnitConverter CreateDefault() { var unitConverter = new UnitConverter(); RegisterDefaultConversions(unitConverter); return unitConverter; } private ConcurrentDictionary ConversionFunctions { get; } /// /// Registers the default conversion functions in the given instance. /// /// The to register the default conversion functions in. private static void RegisterDefaultConversions(UnitConverter unitConverter) { if (unitConverter is null) throw new ArgumentNullException(nameof(unitConverter)); Quantity.DefaultProvider.RegisterUnitConversions(unitConverter); } /// /// Sets the conversion function from two units of the same quantity type. /// /// The type of quantity, must implement . /// From unit enum value, such as . /// To unit enum value, such as . /// The quantity conversion function. public void SetConversionFunction(Enum from, Enum to, ConversionFunction conversionFunction) where TQuantity : IQuantity { var quantityType = typeof(TQuantity); var conversionLookup = new ConversionFunctionLookupKey(quantityType, from, quantityType, to); SetConversionFunction(conversionLookup, conversionFunction); } /// /// Sets the conversion function from two units of different quantity types. /// /// From quantity type, must implement . /// To quantity type, must implement . /// From unit enum value, such as . /// To unit enum value, such as . /// The quantity conversion function. public void SetConversionFunction(Enum from, Enum to, ConversionFunction conversionFunction) where TQuantityFrom : IQuantity where TQuantityTo : IQuantity { SetConversionFunction(typeof(TQuantityFrom), from, typeof(TQuantityTo), to, conversionFunction); } /// /// Sets the conversion function from two units of different quantity types. /// /// From quantity type, must implement . /// From unit enum value, such as . /// To quantity type, must implement . /// To unit enum value, such as . /// The quantity conversion function. public void SetConversionFunction(Type fromType, Enum from, Type toType, Enum to, ConversionFunction conversionFunction) { var conversionLookup = new ConversionFunctionLookupKey(fromType, from, toType, to); SetConversionFunction(conversionLookup, conversionFunction); } /// /// Sets the conversion function for a particular conversion function lookup. /// /// The lookup key. /// The quantity conversion function. internal void SetConversionFunction(ConversionFunctionLookupKey lookupKey, ConversionFunction conversionFunction) { ConversionFunctions[lookupKey] = conversionFunction; } /// /// Sets the conversion function for a particular conversion function lookup. /// /// The quantity type, must implement . /// The quantity conversion function lookup key. /// The quantity conversion function. internal void SetConversionFunction(ConversionFunctionLookupKey conversionLookup, ConversionFunction conversionFunction) where TQuantity : IQuantity { IQuantity TypelessConversionFunction(IQuantity quantity) => conversionFunction((TQuantity) quantity); ConversionFunctions[conversionLookup] = TypelessConversionFunction; } /// /// Gets the conversion function from two units of the same quantity type. /// /// The quantity type, must implement . /// From unit enum value, such as . /// To unit enum value, such as . /// public ConversionFunction GetConversionFunction(Enum from, Enum to) where TQuantity : IQuantity { return GetConversionFunction(typeof(TQuantity), from, typeof(TQuantity), to); } /// /// Gets the conversion function from two units of different quantity types. /// /// From quantity type, must implement . /// To quantity type, must implement . /// From unit enum value, such as . /// To unit enum value, such as . /// public ConversionFunction GetConversionFunction(Enum from, Enum to) where TQuantityFrom : IQuantity where TQuantityTo : IQuantity { return GetConversionFunction(typeof(TQuantityFrom), from, typeof(TQuantityTo), to); } /// /// Gets the conversion function from two units of different quantity types. /// /// From quantity type, must implement . /// From unit enum value, such as . /// To quantity type, must implement . /// To unit enum value, such as . public ConversionFunction GetConversionFunction(Type fromType, Enum from, Type toType, Enum to) { var conversionLookup = new ConversionFunctionLookupKey(fromType, from, toType, to); return GetConversionFunction(conversionLookup); } /// /// Gets the conversion function by its lookup key. /// /// internal ConversionFunction GetConversionFunction(ConversionFunctionLookupKey lookupKey) { IQuantity EchoFunction(IQuantity fromQuantity) => fromQuantity; // If from/to units and to quantity types are equal, then return a function that echoes the input quantity // in order to not have to map conversion functions to "self". if (lookupKey.Item1 == lookupKey.Item3 && Equals(lookupKey.Item2, lookupKey.Item4)) return EchoFunction; return ConversionFunctions[lookupKey]; } /// /// Gets the conversion function for two units of the same quantity type. /// /// The quantity type, must implement . /// From unit enum value, such as . /// To unit enum value, such as . /// The quantity conversion function. /// true if set; otherwise, false. public bool TryGetConversionFunction(Enum from, Enum to, [NotNullWhen(true)] out ConversionFunction? conversionFunction) where TQuantity : IQuantity { return TryGetConversionFunction(typeof(TQuantity), from, typeof(TQuantity), to, out conversionFunction); } /// /// Gets the conversion function for two units of different quantity types. /// /// From quantity type, must implement . /// To quantity type, must implement . /// From unit enum value, such as . /// To unit enum value, such as . /// The quantity conversion function. /// true if set; otherwise, false. public bool TryGetConversionFunction(Enum from, Enum to, [NotNullWhen(true)] out ConversionFunction? conversionFunction) where TQuantityFrom : IQuantity where TQuantityTo : IQuantity { return TryGetConversionFunction(typeof(TQuantityFrom), from, typeof(TQuantityTo), to, out conversionFunction); } /// /// Try to get the conversion function for two units of the same quantity type. /// /// From quantity type, must implement . /// From unit enum value, such as . /// To quantity type, must implement . /// To unit enum value, such as . /// The quantity conversion function. /// true if set; otherwise, false. public bool TryGetConversionFunction(Type fromType, Enum from, Type toType, Enum to, [NotNullWhen(true)] out ConversionFunction? conversionFunction) { var conversionLookup = new ConversionFunctionLookupKey(fromType, from, toType, to); return TryGetConversionFunction(conversionLookup, out conversionFunction); } /// /// /// /// /// /// true if set; otherwise, false. public bool TryGetConversionFunction(ConversionFunctionLookupKey lookupKey, [NotNullWhen(true)] out ConversionFunction? conversionFunction) { return ConversionFunctions.TryGetValue(lookupKey, out conversionFunction); } /// /// Convert between any two quantity units given a numeric value and two unit enum values. /// /// Numeric value. /// From unit enum value. /// To unit enum value, must be compatible with . /// The converted value in the new unit representation. public static double Convert(double fromValue, Enum fromUnitValue, Enum toUnitValue) { return Quantity .From(fromValue, fromUnitValue) .As(toUnitValue); } /// /// Try to convert between any two quantity units given a numeric value and two unit enum values. /// /// Numeric value. /// From unit enum value. /// To unit enum value, must be compatible with . /// The converted value, if successful. Otherwise default. /// True if successful. public static bool TryConvert(double fromValue, Enum fromUnitValue, Enum toUnitValue, out double convertedValue) { convertedValue = 0; if (!Quantity.TryFrom(fromValue, fromUnitValue, out IQuantity? fromQuantity)) return false; try { // We're not going to implement TryAs() in all quantities, so let's just try-catch here convertedValue = fromQuantity.As(toUnitValue); return true; } catch { return false; } } /// /// Convert between any two quantity units by their names, such as converting a "Length" of N "Meter" to "Centimeter". /// This is particularly useful for creating things like a generated unit conversion UI, /// where you list some selectors: /// a) Quantity: Length, Mass, Force etc. /// b) From unit: Meter, Centimeter etc if Length is selected /// c) To unit: Meter, Centimeter etc if Length is selected /// /// /// Input value, which together with represents the quantity to /// convert from. /// /// The invariant quantity name, such as "Length". Does not support localization. /// The invariant unit enum name, such as "Meter". Does not support localization. /// The invariant unit enum name, such as "Meter". Does not support localization. /// double centimeters = ConvertByName(5, "Length", "Meter", "Centimeter"); // 500 /// Output value as the result of converting to . /// /// Thrown when no quantity information is found for the specified quantity name. /// /// No units match the provided unit name. /// More than one unit matches the abbreviation. public static double ConvertByName(double fromValue, string quantityName, string fromUnitName, string toUnitName) { QuantityInfoLookup quantities = UnitsNetSetup.Default.Quantities; UnitInfo fromUnit = quantities.GetUnitByName(quantityName, fromUnitName); UnitInfo toUnit = quantities.GetUnitByName(quantityName, toUnitName); return Quantity.From(fromValue, fromUnit.Value).As(toUnit.Value); } /// /// Convert between any two quantity units by their names, such as converting a "Length" of N "Meter" to "Centimeter". /// This is particularly useful for creating things like a generated unit conversion UI, /// where you list some selectors: /// a) Quantity: Length, Mass, Force etc. /// b) From unit: Meter, Centimeter etc if Length is selected /// c) To unit: Meter, Centimeter etc if Length is selected /// /// /// Input value, which together with represents the quantity to /// convert from. /// /// The invariant quantity name, such as "Length". Does not support localization. /// The invariant unit enum name, such as "Meter". Does not support localization. /// The invariant unit enum name, such as "Meter". Does not support localization. /// Result if conversion was successful, 0 if not. /// bool ok = TryConvertByName(5, "Length", "Meter", "Centimeter", out double centimeters); // 500 /// True if conversion was successful. public static bool TryConvertByName(double inputValue, string quantityName, string fromUnit, string toUnit, out double result) { QuantityInfoLookup quantities = UnitsNetSetup.Default.Quantities; if (quantities.TryGetUnitByName(quantityName, fromUnit, out UnitInfo? fromUnitInfo) && quantities.TryGetUnitByName(quantityName, toUnit, out UnitInfo? toUnitInfo) && Quantity.TryFrom(inputValue, fromUnitInfo.Value, out IQuantity? quantity)) { result = quantity.As(toUnitInfo.Value); return true; } result = 0d; return false; } /// /// Convert between any two quantity units by their abbreviations, such as converting a "Length" of N "m" to "cm". /// This is particularly useful for creating things like a generated unit conversion UI, /// where you list some selectors: /// a) Quantity: Length, Mass, Force etc. /// b) From unit: Meter, Centimeter etc if Length is selected /// c) To unit: Meter, Centimeter etc if Length is selected /// /// /// Input value, which together with represents the quantity to /// convert from. /// /// The invariant quantity name, such as "Length". Does not support localization. /// The abbreviation of the unit in the thread's current culture, such as "m". /// The abbreviation of the unit in the thread's current culture, such as "m". /// double centimeters = ConvertByName(5, "Length", "m", "cm"); // 500 /// Output value as the result of converting to . /// /// Thrown when no quantity information is found for the specified quantity name. /// /// No units match the abbreviation. /// More than one unit matches the abbreviation. public static double ConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev) { return ConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, (IFormatProvider?)null); } /// /// Convert between any two quantity units by their abbreviations, such as converting a "Length" of N "m" to "cm". /// This is particularly useful for creating things like a generated unit conversion UI, /// where you list some selectors: /// a) Quantity: Length, Mass, Force etc. /// b) From unit: Meter, Centimeter etc if Length is selected /// c) To unit: Meter, Centimeter etc if Length is selected /// /// /// Input value, which together with represents the quantity to /// convert from. /// /// The invariant quantity name, such as "Length". Does not support localization. /// The abbreviation of the unit in the given culture, such as "m". /// The abbreviation of the unit in the given culture, such as "m". /// Culture to parse abbreviations with. /// double centimeters = ConvertByName(5, "Length", "m", "cm"); // 500 /// Output value as the result of converting to . /// /// Thrown when no quantity information is found for the specified quantity name. /// /// No units match the abbreviation. /// More than one unit matches the abbreviation. [Obsolete("Methods accepting a culture name are deprecated in favor of using an instance of the IFormatProvider.")] public static double ConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, string? culture) { return ConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, CultureHelper.GetCultureOrInvariant(culture)); } /// /// Convert between any two quantity units by their abbreviations, such as converting a "Length" of N "m" to "cm". /// This is particularly useful for creating things like a generated unit conversion UI, /// where you list some selectors: /// a) Quantity: Length, Mass, Force etc. /// b) From unit: Meter, Centimeter etc if Length is selected /// c) To unit: Meter, Centimeter etc if Length is selected /// /// /// Input value, which together with represents the quantity to /// convert from. /// /// The invariant quantity name, such as "Length". Does not support localization. /// The abbreviation of the unit in the given culture, such as "m". /// The abbreviation of the unit in the given culture, such as "m". /// /// The format provider to use for lookup. Defaults to /// if null. /// /// double centimeters = ConvertByName(5, "Length", "m", "cm"); // 500 /// Output value as the result of converting to . /// /// Thrown when no quantity information is found for the specified quantity name. /// /// No units match the abbreviation. /// More than one unit matches the abbreviation. public static double ConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, IFormatProvider? formatProvider) { QuantityInfoLookup quantities = UnitsNetSetup.Default.Quantities; UnitParser unitParser = UnitsNetSetup.Default.UnitParser; QuantityInfo quantityInfo = quantities.GetQuantityByName(quantityName); Enum fromUnit = unitParser.Parse(fromUnitAbbrev, quantityInfo.UnitType, formatProvider); // ex: ("m", LengthUnit) => LengthUnit.Meter Enum toUnit = unitParser.Parse(toUnitAbbrev, quantityInfo.UnitType, formatProvider); // ex:("cm", LengthUnit) => LengthUnit.Centimeter return Quantity.From(fromValue, fromUnit).As(toUnit); } /// /// Convert between any two quantity units by their abbreviations, such as converting a "Length" of N "m" to "cm". /// This is particularly useful for creating things like a generated unit conversion UI, /// where you list some selectors: /// a) Quantity: Length, Mass, Force etc. /// b) From unit: Meter, Centimeter etc if Length is selected /// c) To unit: Meter, Centimeter etc if Length is selected /// /// /// Input value, which together with represents the quantity to /// convert from. /// /// The invariant quantity name, such as "Length". Does not support localization. /// The abbreviation of the unit in the thread's current culture, such as "m". /// The abbreviation of the unit in the thread's current culture, such as "m". /// Result if conversion was successful, 0 if not. /// double centimeters = ConvertByName(5, "Length", "m", "cm"); // 500 /// True if conversion was successful. public static bool TryConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, out double result) { return TryConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, out result, (IFormatProvider?)null); } /// /// Convert between any two quantity units by their abbreviations, such as converting a "Length" of N "m" to "cm". /// This is particularly useful for creating things like a generated unit conversion UI, /// where you list some selectors: /// a) Quantity: Length, Mass, Force etc. /// b) From unit: Meter, Centimeter etc if Length is selected /// c) To unit: Meter, Centimeter etc if Length is selected /// /// /// Input value, which together with represents the quantity to /// convert from. /// /// The invariant quantity name, such as "Length". Does not support localization. /// The abbreviation of the unit in the given culture, such as "m". /// The abbreviation of the unit in the given culture, such as "m". /// Culture to parse abbreviations with. /// Result if conversion was successful, 0 if not. /// double centimeters = ConvertByName(5, "Length", "m", "cm"); // 500 /// True if conversion was successful. [Obsolete("Methods accepting a culture name are deprecated in favor of using an instance of the IFormatProvider.")] public static bool TryConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, out double result, string? culture) { return TryConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, out result, CultureHelper.GetCultureOrInvariant(culture)); } /// /// Convert between any two quantity units by their abbreviations, such as converting a "Length" of N "m" to "cm". /// This is particularly useful for creating things like a generated unit conversion UI, /// where you list some selectors: /// a) Quantity: Length, Mass, Force etc. /// b) From unit: Meter, Centimeter etc if Length is selected /// c) To unit: Meter, Centimeter etc if Length is selected /// /// /// Input value, which together with represents the quantity to /// convert from. /// /// The invariant quantity name, such as "Length". Does not support localization. /// The abbreviation of the unit in the given culture, such as "m". /// The abbreviation of the unit in the given culture, such as "m". /// /// The format provider to use for lookup. Defaults to /// if null. /// /// Result if conversion was successful, 0 if not. /// double centimeters = ConvertByName(5, "Length", "m", "cm"); // 500 /// True if conversion was successful. public static bool TryConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, out double result, IFormatProvider? formatProvider) { QuantityInfoLookup quantities = UnitsNetSetup.Default.Quantities; UnitParser unitParser = UnitsNetSetup.Default.UnitParser; if (!quantities.TryGetQuantityByName(quantityName, out QuantityInfo? quantityInfo) ) { result = 0; return false; } if (!unitParser.TryParse(fromUnitAbbrev, quantityInfo.UnitType, formatProvider, out Enum? fromUnit) || !unitParser.TryParse(toUnitAbbrev, quantityInfo.UnitType, formatProvider, out Enum? toUnit)) { result = 0; return false; } result = Quantity.From(fromValue, fromUnit).As(toUnit); return true; } } }