// 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
}