// 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.Globalization; using System.Linq; using UnitsNet.InternalHelpers; #if NET using System.Collections.Frozen; #endif namespace UnitsNet; /// /// Convert between units of a quantity, such as converting from meters to centimeters of a given length. /// public class UnitConverter { #if NET private readonly FrozenSet _quantityConversions; #else private readonly HashSet _quantityConversions; #endif /// /// Initializes a new instance of the class with the specified /// . /// /// The used for parsing units. public UnitConverter(UnitParser unitParser) : this(unitParser, unitParser.Quantities.GetQuantityConversions(Quantity.DefaultProvider.Conversions)) { UnitParser = unitParser; } internal UnitConverter(UnitParser unitParser, IEnumerable quantityConversions) { UnitParser = unitParser; #if NET _quantityConversions = quantityConversions.ToFrozenSet(); #else _quantityConversions = [..quantityConversions]; #endif } /// /// Gets the instance used to parse unit abbreviations. /// private UnitParser UnitParser { get; } /// /// Gets the lookup for quantity information, which provides access to details about available quantities, /// their associated units, and related metadata. /// /// /// It enables retrieval of unit and quantity information for conversion operations. /// public QuantityInfoLookup Quantities { get => UnitParser.Quantities; } /// /// Gets the default instance of the class. /// /// /// The default instance configured by . /// public static UnitConverter Default => UnitsNetSetup.Default.UnitConverter; /// /// Creates a new instance of the class. /// /// The parser used to interpret unit abbreviations and symbols. /// /// Options for building the quantity converter, including whether to freeze /// the converter. /// /// A new instance of configured according to the provided options. /// /// If specifies freezing, a /// is created. /// Otherwise, a is created. /// /// /// Thrown when failing to create a conversion expression for one of the user-specified quantity-conversions. /// public static UnitConverter Create(UnitParser unitParser, QuantityConverterBuildOptions options) { if (options is { DefaultCachingMode: ConversionCachingMode.None, CustomQuantityOptions.Count: 0, QuantityConversionOptions: null, Freeze: true }) { return new NoCachingConverter(unitParser); // this is just a sealed version of the default converter } QuantityInfoLookup quantityLookup = unitParser.Quantities; IEnumerable> unitConversionFunctions = quantityLookup.Infos.GetUnitConversionFunctions(options.DefaultCachingMode, options.ReduceConstants, options.CustomQuantityOptions); var quantityConversions = new HashSet(quantityLookup.GetQuantityConversions(Quantity.DefaultProvider.Conversions)); IEnumerable> quantityConversionFunctions; if (options.QuantityConversionOptions is { } customConversionOptions) { Dictionary conversionOptions = quantityLookup.GetQuantityConversionMappingOptions(customConversionOptions); quantityConversions.UnionWith(quantityLookup.GetQuantityConversions(customConversionOptions.CustomConversions).Concat(conversionOptions.Keys)); quantityConversionFunctions = quantityConversions.GetConversionFunctions(conversionOptions, options.DefaultCachingMode, options.ReduceConstants, options.CustomQuantityOptions); } else { quantityConversionFunctions = quantityConversions.GetConversionFunctions(options.DefaultCachingMode, options.ReduceConstants, options.CustomQuantityOptions); } if (options.Freeze) { return new FrozenQuantityConverter(unitParser, quantityConversions, unitConversionFunctions, quantityConversionFunctions); } return new DynamicQuantityConverter(unitParser, quantityConversions, unitConversionFunctions, quantityConversionFunctions, options.ReduceConstants); } /// /// Attempts to convert the specified quantity to the specified unit. /// /// The quantity to convert. Must not be null. /// The unit to convert the quantity to. /// /// When this method returns, contains the converted quantity if the conversion succeeded, or null if the /// conversion failed. /// /// /// true if the conversion succeeded; otherwise, false. /// public bool TryConvertTo(TQuantity quantity, UnitKey toUnitKey, [NotNullWhen(true)] out IQuantity? convertedQuantity) where TQuantity : IQuantity { #pragma warning disable CS0618 // Type or member is obsolete QuantityInfo quantityInfo = quantity.QuantityInfo; #pragma warning restore CS0618 // Type or member is obsolete if (quantityInfo.UnitType == toUnitKey.UnitEnumType) { if (TryConvertValue(quantity.Value, quantity.UnitKey, toUnitKey, out QuantityValue convertedValue)) { convertedQuantity = quantityInfo.From(convertedValue, toUnitKey); return true; } } else if (TryGetUnitInfo(quantity.UnitKey, out UnitInfo? fromUnitInfo) && TryGetUnitInfo(toUnitKey, out UnitInfo? toUnitInfo) && TryConvertValueInternal(quantity.Value, fromUnitInfo, toUnitInfo, out QuantityValue convertedValue)) { convertedQuantity = toUnitInfo.From(convertedValue); return true; } convertedQuantity = null; return false; } /// /// Attempts to convert a quantity to a specified unit. /// /// The type of the quantity. /// The type of the unit. /// The quantity to convert. /// The unit to convert to. /// /// When this method returns, contains the converted quantity if the conversion succeeded, or null if the /// conversion failed. /// /// /// true if the conversion succeeded; otherwise, false. /// public bool TryConvertToUnit(TQuantity quantity, TUnit toUnit, [NotNullWhen(true)] out TQuantity? convertedQuantity) where TQuantity : IQuantity where TUnit : struct, Enum { if (TryConvertValue(quantity.Value, quantity.Unit, toUnit, out QuantityValue convertedValue)) { #if NET QuantityInfo quantityInfo = TQuantity.Info; #else QuantityInfo quantityInfo = quantity.QuantityInfo; #endif convertedQuantity = quantityInfo.From(convertedValue, toUnit); return true; } convertedQuantity = default; return false; } /// /// Attempts to retrieve information about a specified unit. /// /// The unit for which information is being requested. /// /// When this method returns, contains the unit information associated with the specified unit, /// if the unit is found; otherwise, null. This parameter is passed uninitialized. /// /// /// true if the unit information was found; otherwise, false. /// protected bool TryGetUnitInfo(UnitKey unit, [NotNullWhen(true)] out UnitInfo? unitInfo) { return Quantities.TryGetUnitInfo(unit, out unitInfo); } /// /// Determines whether a conversion is defined between the specified source and target quantities. /// /// The source quantity information. /// The target quantity information. /// /// true if a conversion is defined between the source and target quantities; otherwise, false. /// protected bool ConversionDefined(QuantityInfo sourceQuantity, QuantityInfo targetQuantity) { return _quantityConversions.Contains(new QuantityConversion(sourceQuantity, targetQuantity)); } /// /// Gets the collection of quantity conversions available in this . /// /// /// A quantity conversion defines the relationship between two quantities, enabling conversions /// between them. This property provides access to all such conversions configured for this instance. /// internal IReadOnlyCollection QuantityConversions => _quantityConversions; /// /// Attempts to convert the specified quantity to a different unit. /// /// The type of the quantity to be converted. /// The type of the unit to convert to. /// The quantity to be converted. /// The unit to convert the quantity to. /// /// When this method returns, contains the converted value if the conversion succeeded; otherwise, the default value /// for the type of the converted value. /// /// /// true if the conversion succeeded; otherwise, false. /// public bool TryConvertValue(TQuantity quantity, TUnit toUnit, out QuantityValue convertedValue) where TQuantity : IQuantity where TUnit : struct, Enum { return TryConvertValue(quantity.Value, quantity.Unit, toUnit, out convertedValue); } /// /// Attempts to convert a quantity value from one unit to another. /// /// The type of the unit enumeration. /// The quantity value to be converted. /// The unit of the quantity value to convert from. /// The unit of the quantity value to convert to. /// /// When this method returns, contains the converted quantity value if the conversion succeeded, /// or null if the conversion failed. This parameter is passed uninitialized. /// /// /// true if the conversion succeeded; otherwise, false. /// public virtual bool TryConvertValue(QuantityValue value, TUnit fromUnit, TUnit toUnit, out QuantityValue convertedValue) where TUnit : struct, Enum { var fromUnitKey = UnitKey.ForUnit(fromUnit); var toUnitKey = UnitKey.ForUnit(toUnit); if (fromUnitKey == toUnitKey) { convertedValue = value; return true; } if (TryGetUnitInfo(fromUnitKey, out UnitInfo? fromUnitInfo) && TryGetUnitInfo(toUnitKey, out UnitInfo? toUnitInfo)) { convertedValue = toUnitInfo.GetValueFrom(value, fromUnitInfo); return true; } convertedValue = default; return false; } /// /// Attempts to convert a quantity value from one unit to another. /// /// The value to be converted. /// The unit of the value to be converted from. /// The unit of the value to be converted to. /// /// When this method returns, contains the converted value if the conversion succeeded, /// or null if the conversion failed. This parameter is passed uninitialized. /// /// /// true if the conversion succeeded; otherwise, false. /// public virtual bool TryConvertValue(QuantityValue value, UnitKey fromUnitKey, UnitKey toUnitKey, out QuantityValue convertedValue) { if (fromUnitKey == toUnitKey) { convertedValue = value; return true; } if (TryGetUnitInfo(fromUnitKey, out UnitInfo? fromUnitInfo) && TryGetUnitInfo(toUnitKey, out UnitInfo? toUnitInfo)) { return TryConvertValueInternal(value, fromUnitInfo, toUnitInfo, out convertedValue); } convertedValue = default; return false; } /// /// Attempts to convert a quantity value from one unit to another. /// /// The quantity value to convert. /// The unit information of the source unit. /// The unit information of the target unit. /// /// When this method returns, contains the converted quantity value if the conversion succeeded, /// or null if the conversion failed. This parameter is passed uninitialized. /// /// /// true if the conversion succeeded; otherwise, false. /// /// /// This method should succeed for any two units of the same quantity, but may fail if the units are from two /// incompatible quantities. /// protected virtual bool TryConvertValueInternal(QuantityValue value, UnitInfo fromUnitInfo, UnitInfo toUnitInfo, out QuantityValue convertedValue) { if (fromUnitInfo.QuantityInfo != toUnitInfo.QuantityInfo) { return TryConvertValueFromOneQuantityToAnother(value, fromUnitInfo, toUnitInfo, out convertedValue); } convertedValue = toUnitInfo.GetValueFrom(value, fromUnitInfo); return true; } /// /// Attempts to convert a value from one quantity to another. /// /// The value to be converted. /// The unit of the value to be converted. /// The unit to which the value should be converted. /// The converted value if the conversion is successful; otherwise, the default value. /// /// true if the conversion is successful; otherwise, false. /// /// /// This method handles conversions between quantities with matching or inverse base dimensions. /// protected bool TryConvertValueFromOneQuantityToAnother(QuantityValue value, UnitInfo fromUnit, UnitInfo toUnit, out QuantityValue convertedValue) { QuantityInfo sourceQuantity = fromUnit.QuantityInfo; QuantityInfo targetQuantity = toUnit.QuantityInfo; if (!ConversionDefined(sourceQuantity, targetQuantity)) { convertedValue = default; return false; } ConvertValueDelegate conversionExpression; if (sourceQuantity.BaseDimensions.IsInverseOf(targetQuantity.BaseDimensions)) { conversionExpression = QuantityValue.Inverse; } else if (sourceQuantity.BaseDimensions == targetQuantity.BaseDimensions) { if (sourceQuantity.BaseDimensions.IsDimensionless()) { convertedValue = toUnit.ConvertValueFromBaseUnit(fromUnit.ConvertValueToBaseUnit(value)); return true; } conversionExpression = sourceValue => sourceValue; } else { convertedValue = default; return false; } if (fromUnit.BaseUnits != BaseUnits.Undefined) { if (toUnit.BaseUnits == fromUnit.BaseUnits) { convertedValue = conversionExpression(value); return true; } if (targetQuantity.UnitInfos.TryGetUnitWithBase(fromUnit.BaseUnits, out UnitInfo? matchingUnit)) { convertedValue = ConvertValueInternal(conversionExpression(value), matchingUnit, toUnit); return true; } UnitInfo fromBaseUnit = sourceQuantity.BaseUnitInfo; if (fromUnit.BaseUnits != fromBaseUnit.BaseUnits && fromBaseUnit.BaseUnits != BaseUnits.Undefined) { if (toUnit.QuantityInfo.BaseUnitInfo.BaseUnits == fromBaseUnit.BaseUnits) { convertedValue = toUnit.ConvertValueFromBaseUnit(conversionExpression(fromUnit.ConversionToBase.Evaluate(value))); return true; } } } else if (toUnit.BaseUnits != BaseUnits.Undefined) { if (sourceQuantity.UnitInfos.TryGetUnitWithBase(toUnit.BaseUnits, out UnitInfo? matchingUnit)) { convertedValue = conversionExpression(ConvertValueInternal(value, fromUnit, matchingUnit)); return true; } UnitInfo toBaseUnit = targetQuantity.BaseUnitInfo; if (toUnit.BaseUnits != toBaseUnit.BaseUnits && toBaseUnit.BaseUnits != BaseUnits.Undefined) { if (sourceQuantity.BaseUnitInfo.BaseUnits == toBaseUnit.BaseUnits) { convertedValue = toUnit.ConversionFromBase.Evaluate(conversionExpression(fromUnit.ConvertValueToBaseUnit(value))); return true; } } } foreach (UnitInfo sourceUnit in sourceQuantity.UnitInfos) { if (sourceUnit.BaseUnits == BaseUnits.Undefined || sourceUnit.BaseUnits == fromUnit.BaseUnits) { continue; } if (targetQuantity.UnitInfos.TryGetUnitWithBase(sourceUnit.BaseUnits, out UnitInfo? matchingUnit)) { convertedValue = ConvertValueInternal(conversionExpression(ConvertValueInternal(value, fromUnit, sourceUnit)), matchingUnit, toUnit); return true; } } convertedValue = default; return false; } /// /// Attempts to convert a quantity value from a source unit to a target quantity type and unit. /// /// The type of the source unit enumeration. /// The type of the target quantity. /// The type of the target unit enumeration. /// The quantity value to convert. /// The source unit of the quantity value. /// The information about the target quantity type and unit. /// /// When this method returns, contains the converted quantity if the conversion succeeded, or null if the /// conversion failed. /// /// true if the conversion succeeded; otherwise, false. public virtual bool TryConvertTo(QuantityValue value, TSourceUnit fromUnit, QuantityInfo targetQuantityInfo, [MaybeNullWhen(false)] out TTargetQuantity convertedQuantity) where TSourceUnit : struct, Enum where TTargetQuantity : IQuantity where TTargetUnit : struct, Enum { if (TryGetUnitInfo(UnitKey.ForUnit(fromUnit), out UnitInfo? fromUnitInfo) && ConversionDefined(fromUnitInfo.QuantityInfo, targetQuantityInfo)) { return targetQuantityInfo.TryConvertFrom(value, fromUnitInfo, out convertedQuantity); } convertedQuantity = default; return false; } /// public virtual bool TryConvertTo(QuantityValue value, UnitKey fromUnitKey, QuantityInfo targetQuantityInfo, [NotNullWhen(true)] out IQuantity? convertedQuantity) { if (TryGetUnitInfo(fromUnitKey, out UnitInfo? fromUnitInfo) && ConversionDefined(fromUnitInfo.QuantityInfo, targetQuantityInfo)) { return targetQuantityInfo.TryConvertFrom(value, fromUnitInfo, out convertedQuantity); } convertedQuantity = null; return false; } /// /// Converts the quantity to the specified unit. /// /// The quantity to convert. Must not be null. /// The unit to convert the quantity to. /// When this method returns, contains the converted quantity if the conversion succeeded, or /// null /// if the /// conversion failed. /// /// A new instance of representing the converted value in the specified unit. /// /// Thrown when no unit information is found for the specified enum value. /// /// Thrown when the conversion between the specified units is not possible. /// /// /// This method should succeed for any two units of the same quantity, but may fail if the units are not found or are /// found to be from two /// incompatible quantities. /// public IQuantity ConvertTo(TQuantity quantity, UnitKey toUnitKey) where TQuantity : IQuantity { #pragma warning disable CS0618 // Type or member is obsolete // the alternative is to use Quantities.GetQuantityInfo(quantity.GetType()); QuantityInfo quantityInfo = quantity.QuantityInfo; #pragma warning restore CS0618 // Type or member is obsolete if (quantityInfo.UnitType == toUnitKey.UnitEnumType) { QuantityValue convertedValue = ConvertValue(quantity.Value, quantity.UnitKey, toUnitKey); return quantityInfo.From(convertedValue, toUnitKey); } else { UnitInfo fromUnitInfo = GetUnitInfo(quantity.UnitKey); UnitInfo toUnitInfo = GetUnitInfo(toUnitKey); QuantityValue convertedValue = ConvertValueInternal(quantity.Value, fromUnitInfo, toUnitInfo); return toUnitInfo.From(convertedValue); } } /// /// Converts a quantity from its current unit to the specified unit. /// /// /// The type of the quantity being converted. Must implement . /// /// /// The type of the unit to convert to. Must be an enumeration. /// /// /// The quantity to be converted, including its value and current unit. /// /// /// The target unit to which the quantity should be converted. /// /// /// A new instance of representing the converted value in the specified unit. /// /// Thrown when an unknown unit is provided. public TQuantity ConvertToUnit(TQuantity quantity, TUnit toUnit) where TQuantity : IQuantity where TUnit : struct, Enum { QuantityValue convertedValue = ConvertValue(quantity.Value, quantity.Unit, toUnit); #if NET QuantityInfo quantityInfo = TQuantity.Info; #else QuantityInfo quantityInfo = quantity.QuantityInfo; #endif return quantityInfo.From(convertedValue, toUnit); } /// /// Retrieves information about a specific unit. /// /// The unit for which information is being requested. /// An object containing details about the specified unit. /// Thrown when the is null. /// Thrown when the is not recognized. protected UnitInfo GetUnitInfo(UnitKey unit) { return Quantities.GetUnitInfo(unit); } /// /// Converts the value of a quantity to a specified unit. /// /// The type of quantity to use as the source of the conversion. /// The type of the unit to convert to. Must be a struct and an enum. /// The quantity to convert. /// The unit to convert the quantity to. /// The converted quantity value. /// Thrown when the is null. /// Thrown when the conversion is not possible. /// Thrown when is not found. public QuantityValue ConvertValue(TQuantity quantity, TUnit toUnit) where TQuantity : IQuantity where TUnit : struct, Enum { return ConvertValue(quantity.Value, quantity.Unit, toUnit); } /// /// Converts a quantity value from one unit to another using the specified quantity information. /// /// The type of the unit. /// The value to be converted. /// The unit of the value to be converted. /// The unit to which the value should be converted. /// Thrown when either or is not found. /// The converted quantity value. public virtual QuantityValue ConvertValue(QuantityValue value, TUnit fromUnit, TUnit toUnit) where TUnit : struct, Enum { var fromUnitKey = UnitKey.ForUnit(fromUnit); var toUnitKey = UnitKey.ForUnit(toUnit); if (fromUnitKey.UnitEnumValue == toUnitKey.UnitEnumValue) { return value; } UnitInfo fromUnitInfo = GetUnitInfo(fromUnitKey); UnitInfo toUnitInfo = GetUnitInfo(toUnitKey); return toUnitInfo.GetValueFrom(value, fromUnitInfo); } /// /// Converts a quantity value from one unit to another using the specified quantity information. /// /// The value to be converted. /// The unit of the value to be converted from. /// The unit of the value to be converted to. /// The converted quantity value. /// Thrown when no unit information is found for the specified enum value. /// /// Thrown when the conversion between the specified units is not possible. /// /// /// This method should succeed for any two units of the same quantity, but may fail if the units are not found or are /// found to be from two /// incompatible quantities. /// public virtual QuantityValue ConvertValue(QuantityValue value, UnitKey fromUnitKey, UnitKey toUnitKey) { if (fromUnitKey == toUnitKey) { return value; } UnitInfo fromUnitInfo = GetUnitInfo(fromUnitKey); UnitInfo toUnitInfo = GetUnitInfo(toUnitKey); return ConvertValueInternal(value, fromUnitInfo, toUnitInfo); } /// /// Converts a quantity value from one unit to another. /// /// The quantity value to convert. /// The unit information of the source unit. /// The unit information of the target unit. /// The converted quantity value. /// /// Thrown when the conversion between the specified units is not possible. /// /// /// This method should succeed for any two units of the same quantity, but may fail if the units are from two /// incompatible quantities. /// protected virtual QuantityValue ConvertValueInternal(QuantityValue value, UnitInfo fromUnitInfo, UnitInfo toUnitInfo) { return fromUnitInfo.QuantityInfo == toUnitInfo.QuantityInfo ? toUnitInfo.GetValueFrom(value, fromUnitInfo) : ConvertValueFromOneQuantityToAnother(value, fromUnitInfo, toUnitInfo); } /// /// Converts a value from one quantity to another, considering the units and their base dimensions. /// /// The value to be converted. /// Information about the unit of the source quantity. /// Information about the unit of the target quantity. /// The converted value in the target quantity's unit. /// /// Thrown when no implicit conversion exists between the source and target quantities, /// or when no compatible units are found for the conversion. /// /// /// This method handles conversions between quantities with matching or inverse base dimensions. /// protected QuantityValue ConvertValueFromOneQuantityToAnother(QuantityValue value, UnitInfo fromUnitInfo, UnitInfo toUnitInfo) { QuantityInfo sourceQuantity = fromUnitInfo.QuantityInfo; QuantityInfo targetQuantity = toUnitInfo.QuantityInfo; if (!ConversionDefined(sourceQuantity, targetQuantity)) { throw InvalidConversionException.CreateImplicitConversionException(sourceQuantity, targetQuantity); } ConvertValueDelegate conversionExpression; if (sourceQuantity.BaseDimensions.IsInverseOf(targetQuantity.BaseDimensions)) { conversionExpression = QuantityValue.Inverse; } else if (sourceQuantity.BaseDimensions == targetQuantity.BaseDimensions) { if (sourceQuantity.BaseDimensions.IsDimensionless()) { return toUnitInfo.ConvertValueFromBaseUnit(fromUnitInfo.ConvertValueToBaseUnit(value)); } conversionExpression = sourceValue => sourceValue; } else { throw InvalidConversionException.CreateIncompatibleDimensionsException(sourceQuantity, targetQuantity); } if (fromUnitInfo.BaseUnits != BaseUnits.Undefined) { if (toUnitInfo.BaseUnits == fromUnitInfo.BaseUnits) { return conversionExpression(value); } if (targetQuantity.UnitInfos.TryGetUnitWithBase(fromUnitInfo.BaseUnits, out UnitInfo? matchingUnit)) { return ConvertValueInternal(conversionExpression(value), matchingUnit, toUnitInfo); } UnitInfo fromBaseUnit = sourceQuantity.BaseUnitInfo; if (fromUnitInfo.BaseUnits != fromBaseUnit.BaseUnits && fromBaseUnit.BaseUnits != BaseUnits.Undefined) { if (toUnitInfo.QuantityInfo.BaseUnitInfo.BaseUnits == fromBaseUnit.BaseUnits) { return toUnitInfo.ConvertValueFromBaseUnit(conversionExpression(fromUnitInfo.ConversionToBase.Evaluate(value))); } } } else if (toUnitInfo.BaseUnits != BaseUnits.Undefined) { if (sourceQuantity.UnitInfos.TryGetUnitWithBase(toUnitInfo.BaseUnits, out UnitInfo? matchingUnit)) { return conversionExpression(ConvertValueInternal(value, fromUnitInfo, matchingUnit)); } UnitInfo toBaseUnit = targetQuantity.BaseUnitInfo; if (toUnitInfo.BaseUnits != toBaseUnit.BaseUnits && toBaseUnit.BaseUnits != BaseUnits.Undefined) { if (sourceQuantity.BaseUnitInfo.BaseUnits == toBaseUnit.BaseUnits) { return toUnitInfo.ConversionFromBase.Evaluate(conversionExpression(fromUnitInfo.ConvertValueToBaseUnit(value))); } } } IReadOnlyList sourceQuantityUnits = sourceQuantity.UnitInfos; var nbSourceUnits = sourceQuantityUnits.Count; for (var i = 0; i < nbSourceUnits; i++) { UnitInfo sourceUnit = sourceQuantityUnits[i]; if (sourceUnit.BaseUnits == BaseUnits.Undefined || sourceUnit.BaseUnits == fromUnitInfo.BaseUnits) { continue; } if (targetQuantity.UnitInfos.TryGetUnitWithBase(sourceUnit.BaseUnits, out UnitInfo? matchingUnit)) { return ConvertValueInternal(conversionExpression(ConvertValueInternal(value, fromUnitInfo, sourceUnit)), matchingUnit, toUnitInfo); } } throw InvalidConversionException.CreateIncompatibleUnitsException(fromUnitInfo, targetQuantity); } /// /// Converts a quantity value from one unit to another specified unit type. /// /// The type of the source unit. /// The type of the target quantity. /// The type of the target unit. /// The quantity value to convert. /// The source unit of the quantity value. /// The target quantity information which includes the target unit. /// The converted quantity in the target unit type. /// Thrown when is null. /// Thrown when the source unit is not found. /// Thrown when the conversion cannot be performed. public virtual TTargetQuantity ConvertTo(QuantityValue value, TSourceUnit fromUnit, QuantityInfo targetQuantityInfo) where TSourceUnit : struct, Enum where TTargetQuantity : IQuantity where TTargetUnit : struct, Enum { UnitInfo fromUnitInfo = GetUnitInfo(UnitKey.ForUnit(fromUnit)); QuantityInfo sourceQuantityInfo = fromUnitInfo.QuantityInfo; if (!ConversionDefined(sourceQuantityInfo, targetQuantityInfo)) { throw InvalidConversionException.CreateImplicitConversionException(sourceQuantityInfo, targetQuantityInfo); } return targetQuantityInfo.ConvertFrom(value, fromUnitInfo); } /// public virtual IQuantity ConvertTo(QuantityValue value, UnitKey fromUnitKey, QuantityInfo targetQuantityInfo) { UnitInfo fromUnitInfo = GetUnitInfo(fromUnitKey); QuantityInfo sourceQuantityInfo = fromUnitInfo.QuantityInfo; if (!ConversionDefined(sourceQuantityInfo, targetQuantityInfo)) { throw InvalidConversionException.CreateImplicitConversionException(sourceQuantityInfo, targetQuantityInfo); } return targetQuantityInfo.ConvertFrom(value, fromUnitInfo); } /// /// Gets a conversion function that converts a value from one unit to another. /// /// The key representing the unit to convert from. /// The key representing the unit to convert to. /// /// A delegate that converts a from the specified to /// the specified . /// /// /// Thrown if the specified or does not exist. /// /// /// Thrown if the quantities are different and no conversion function exists between them. /// /// /// If the and are the same, the returned function will /// be an identity function. /// public virtual ConvertValueDelegate GetConversionFunction(UnitKey fromUnitKey, UnitKey toUnitKey) { if (fromUnitKey == toUnitKey) { return value => value; } UnitInfo fromUnitInfo = GetUnitInfo(fromUnitKey); UnitInfo toUnitInfo = GetUnitInfo(toUnitKey); return fromUnitKey.UnitEnumType == toUnitKey.UnitEnumType ? fromUnitInfo.GetUnitConversionExpressionTo(toUnitInfo) : GetConversionFromOneQuantityToAnother(fromUnitInfo, toUnitInfo); } /// /// Retrieves the conversion expression required to convert a value from one unit of quantity to another. /// /// The unit information of the source quantity. /// The unit information of the target quantity. /// /// A boolean value indicating whether to reduce constants in the conversion expression. /// Default is true. /// /// A representing the conversion from the source unit to the target unit. /// /// Thrown when no implicit conversion exists between the specified source and target quantities. /// protected ConversionExpression GetConversionFromOneQuantityToAnother(UnitInfo fromUnitInfo, UnitInfo toUnitInfo, bool reduceConstants = true) { QuantityInfo sourceQuantity = fromUnitInfo.QuantityInfo; QuantityInfo targetQuantity = toUnitInfo.QuantityInfo; if (!ConversionDefined(sourceQuantity, targetQuantity)) { throw InvalidConversionException.CreateImplicitConversionException(sourceQuantity, targetQuantity); } return fromUnitInfo.GetQuantityConversionExpressionTo(toUnitInfo); } /// /// Attempts to get a conversion function that converts a value from one unit to another. /// /// The key representing the unit to convert from. /// The key representing the unit to convert to. /// /// When this method returns, contains the conversion function if the conversion is possible; otherwise, null. /// /// /// true if the conversion function was successfully retrieved; otherwise, false. /// /// /// If the and are the same, the returned function will /// be an identity function. /// public virtual bool TryGetConversionFunction(UnitKey fromUnitKey, UnitKey toUnitKey, [NotNullWhen(true)] out ConvertValueDelegate? conversionFunction) { if (fromUnitKey == toUnitKey) { conversionFunction = value => value; return true; } if (!TryGetUnitInfo(fromUnitKey, out UnitInfo? fromUnitInfo) || !TryGetUnitInfo(toUnitKey, out UnitInfo? toUnitInfo)) { conversionFunction = null; return false; } if (fromUnitKey.UnitEnumType != toUnitKey.UnitEnumType) { return TryGetConversionFromOneQuantityToAnother(fromUnitInfo, toUnitInfo, out conversionFunction); } conversionFunction = fromUnitInfo.GetUnitConversionExpressionTo(toUnitInfo); return true; } /// /// Attempts to retrieve a conversion function that converts a value from one unit to another unit of a different /// quantity. /// /// The unit information of the source unit. /// The unit information of the target unit. /// /// When this method returns, contains the conversion function if the conversion is defined; otherwise, null. /// /// /// true if a conversion function is successfully retrieved; otherwise, false. /// protected bool TryGetConversionFromOneQuantityToAnother(UnitInfo fromUnitInfo, UnitInfo toUnitInfo, out ConvertValueDelegate? conversionFunction) { if (ConversionDefined(fromUnitInfo.QuantityInfo, toUnitInfo.QuantityInfo) && fromUnitInfo.TryGetQuantityConversionExpressionTo(toUnitInfo, true, out ConversionExpression? conversionExpression)) { conversionFunction = conversionExpression; return true; } conversionFunction = null; 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 QuantityValue ConvertValueByName(QuantityValue fromValue, string quantityName, string fromUnitName, string toUnitName) { UnitInfo fromUnitInfo = Quantities.GetUnitByName(quantityName, fromUnitName); UnitInfo toUnitInfo = Quantities.GetUnitByName(quantityName, toUnitName); return ConvertValueInternal(fromValue, fromUnitInfo, toUnitInfo); } /// /// 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 bool TryConvertValueByName(QuantityValue inputValue, string quantityName, string fromUnitName, string toUnitName, out QuantityValue result) { if (Quantities.TryGetUnitByName(quantityName, fromUnitName, out UnitInfo? fromUnitInfo) && Quantities.TryGetUnitByName(quantityName, toUnitName, out UnitInfo? toUnitInfo)) { return TryConvertValueInternal(inputValue, fromUnitInfo, toUnitInfo, out result); } result = default; 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 QuantityValue ConvertValueByAbbreviation(QuantityValue fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev) { return ConvertValueByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, 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". /// The unit localization culture. 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 QuantityValue ConvertValueByAbbreviation(QuantityValue fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, CultureInfo? culture) { QuantityInfo quantityInfo = Quantities.GetQuantityByName(quantityName); UnitInfo fromUnitInfo = UnitParser.Parse(fromUnitAbbrev, quantityInfo.UnitInfos, culture); // ex: ("m", LengthUnit) => LengthUnit.Meter UnitInfo toUnitInfo = UnitParser.Parse(toUnitAbbrev, quantityInfo.UnitInfos, culture); // ex:("cm", LengthUnit) => LengthUnit.Centimeter return ConvertValueInternal(fromValue, fromUnitInfo, toUnitInfo); } /// /// 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 bool TryConvertValueByAbbreviation(QuantityValue fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, out QuantityValue result) { return TryConvertValueByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, null, out result); } /// /// 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 unit localization culture. 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 bool TryConvertValueByAbbreviation(QuantityValue fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, CultureInfo? culture, out QuantityValue result) { if (!Quantities.TryGetQuantityByName(quantityName, out QuantityInfo? quantityInfo)) { result = default; return false; } if (UnitParser.TryParse(fromUnitAbbrev, quantityInfo.UnitInfos, culture, out UnitInfo? fromUnitInfo) && UnitParser.TryParse(toUnitAbbrev, quantityInfo.UnitInfos, culture, out UnitInfo? toUnitInfo)) { result = ConvertValueInternal(fromValue, fromUnitInfo, toUnitInfo); return true; } result = default; return false; } #region Static methods /// public static QuantityValue Convert(QuantityValue value, UnitKey fromUnitKey, UnitKey toUnitKey) { return Default.ConvertValue(value, fromUnitKey, toUnitKey); } /// public static bool TryConvert(QuantityValue value, UnitKey fromUnitKey, UnitKey toUnitKey, out QuantityValue result) { return Default.TryConvertValue(value, fromUnitKey, toUnitKey, out result); } /// public static QuantityValue ConvertByName(QuantityValue inputValue, string quantityName, string fromUnitName, string toUnitName) { return Default.ConvertValueByName(inputValue, quantityName, fromUnitName, toUnitName); } /// public static bool TryConvertByName(QuantityValue inputValue, string quantityName, string fromUnitName, string toUnitName, out QuantityValue result) { return Default.TryConvertValueByName(inputValue, quantityName, fromUnitName, toUnitName, out result); } /// public static QuantityValue ConvertByAbbreviation(QuantityValue inputValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev) { return Default.ConvertValueByAbbreviation(inputValue, quantityName, fromUnitAbbrev, toUnitAbbrev); } /// public static bool TryConvertByAbbreviation(QuantityValue inputValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, out QuantityValue result) { return Default.TryConvertValueByAbbreviation(inputValue, quantityName, fromUnitAbbrev, toUnitAbbrev, out result); } /// public static QuantityValue ConvertByAbbreviation(QuantityValue fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, CultureInfo? culture) { return Default.ConvertValueByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, culture); } /// public static bool TryConvertByAbbreviation(QuantityValue inputValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, CultureInfo? culture, out QuantityValue result) { return Default.TryConvertValueByAbbreviation(inputValue, quantityName, fromUnitAbbrev, toUnitAbbrev, culture, out result); } /// /// 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 CultureInfo.")] public static QuantityValue ConvertByAbbreviation(QuantityValue fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, string? culture) { return Default.ConvertValueByAbbreviation(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 thread's current culture, such as "m". /// The abbreviation of the unit in the thread's current 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 CultureInfo.")] public static bool TryConvertByAbbreviation(QuantityValue inputValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, out QuantityValue result, string? culture) { return Default.TryConvertValueByAbbreviation(inputValue, quantityName, fromUnitAbbrev, toUnitAbbrev, CultureHelper.GetCultureOrInvariant(culture), out result); } #endregion }