Skip to content

✨Configure unit conversions via IQuantityInfo #1578

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Linq;
using CodeGen.Helpers;
using CodeGen.JsonTypes;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace CodeGen.Generators.UnitsNetGen
{
Expand Down Expand Up @@ -71,7 +70,7 @@ namespace UnitsNet
{
Writer.WL(@$"
#if NET7_0_OR_GREATER");
foreach (var relation in _quantity.Relations)
foreach (QuantityRelation relation in _quantity.Relations)
{
if (relation.LeftQuantity == _quantity)
{
Expand Down Expand Up @@ -140,15 +139,15 @@ namespace UnitsNet
}}");
return Writer.ToString();
}

private void GenerateQuantityInfo()
{
var quantityInfoClassName = $"{_quantity.Name}Info";
BaseDimensions baseDimensions = _quantity.BaseDimensions;
var createDimensionsExpression = _isDimensionless
? "BaseDimensions.Dimensionless"
: $"new BaseDimensions({baseDimensions.L}, {baseDimensions.M}, {baseDimensions.T}, {baseDimensions.I}, {baseDimensions.Θ}, {baseDimensions.N}, {baseDimensions.J})";

Writer.WL($@"
/// <summary>
/// Provides detailed information about the <see cref=""{_quantity.Name}""/> quantity, including its name, base unit, unit mappings, base dimensions, and conversion functions.
Expand All @@ -159,7 +158,7 @@ public sealed class {quantityInfoClassName}: QuantityInfo<{_quantity.Name}, {_un
/// <inheritdoc />
public {quantityInfoClassName}(string name, {_unitEnumName} baseUnit, IEnumerable<IUnitDefinition<{_unitEnumName}>> unitMappings, {_quantity.Name} zero, BaseDimensions baseDimensions,
QuantityFromDelegate<{_quantity.Name}, {_unitEnumName}> fromDelegate, ResourceManager? unitAbbreviations)
: base(name, baseUnit, unitMappings, zero, baseDimensions, fromDelegate, unitAbbreviations)
: base(name, baseUnit, unitMappings, zero, baseDimensions, fromDelegate, {_quantity.Name}.RegisterDefaultConversions, unitAbbreviations)
{{
}}

Expand Down Expand Up @@ -208,7 +207,7 @@ public sealed class {quantityInfoClassName}: QuantityInfo<{_quantity.Name}, {_un
/// <returns>An <see cref=""IEnumerable{{T}}""/> of <see cref=""UnitDefinition{{{_unitEnumName}}}""/> representing the default unit mappings for {_quantity.Name}.</returns>
public static IEnumerable<UnitDefinition<{_unitEnumName}>> GetDefaultMappings()
{{");

foreach (Unit unit in _quantity.Units)
{
BaseUnits? baseUnits = unit.BaseUnits;
Expand Down Expand Up @@ -241,7 +240,7 @@ public sealed class {quantityInfoClassName}: QuantityInfo<{_quantity.Name}, {_un
}}
");
}

private void GenerateStaticConstructor()
{
Writer.WL($@"
Expand Down Expand Up @@ -363,7 +362,7 @@ private void GenerateProperties()

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
Enum IQuantity.Unit => Unit;

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
UnitKey IQuantity.UnitKey => UnitKey.ForUnit(Unit);

Expand Down Expand Up @@ -1256,7 +1255,7 @@ public string ToString(string? format, IFormatProvider? provider)
#endregion
" );
}

/// <inheritdoc cref="GetObsoleteAttributeOrNull(string)"/>
private static string? GetObsoleteAttributeOrNull(Quantity quantity) => GetObsoleteAttributeOrNull(quantity.ObsoleteText);

Expand Down
28 changes: 20 additions & 8 deletions UnitsNet.Tests/CustomQuantities/HowMuch.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using UnitsNet.Units;

namespace UnitsNet.Tests.CustomQuantities
namespace UnitsNet.Tests.CustomQuantities
{
/// <inheritdoc cref="IQuantity"/>
/// <summary>
Expand Down Expand Up @@ -30,7 +27,7 @@ public double As(HowMuchUnit unit)
public double Value { get; }

#region IQuantity

public static readonly QuantityInfo<HowMuch, HowMuchUnit> Info = new(
nameof(HowMuch),
HowMuchUnit.Some,
Expand All @@ -42,7 +39,8 @@ public double As(HowMuchUnit unit)
},
new HowMuch(0, HowMuchUnit.Some),
new BaseDimensions(0, 1, 0, 0, 0, 0, 0),
From);
From,
RegisterUnitConversions);

QuantityInfo<HowMuch, HowMuchUnit> IQuantity<HowMuch, HowMuchUnit>.QuantityInfo
{
Expand Down Expand Up @@ -81,7 +79,7 @@ public IQuantity<HowMuchUnit> ToUnit(HowMuchUnit unit)
{
throw new NotImplementedException();
}

IQuantity<HowMuchUnit> IQuantity<HowMuchUnit>.ToUnit(UnitSystem unitSystem)
{
throw new NotImplementedException();
Expand All @@ -99,7 +97,7 @@ public bool Equals(HowMuch other, HowMuch tolerance)
{
throw new NotImplementedException();
}

#if !NET

QuantityInfo IQuantity.QuantityInfo
Expand All @@ -110,5 +108,19 @@ QuantityInfo IQuantity.QuantityInfo
Enum IQuantity.Unit => Unit;
#endif
#endregion

internal static void RegisterUnitConversions(UnitConverter unitConverter)
{
// Register in unit converter: HowMuchUnit -> BaseUnit
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.ATon, HowMuchUnit.Some, howMuch => new HowMuch(howMuch.Value * 1e3, HowMuchUnit.Some));
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.AShitTon, HowMuchUnit.Some, howMuch => new HowMuch(howMuch.Value * 1e6, HowMuchUnit.Some));

// Register in unit converter: BaseUnit <-> BaseUnit
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Some, HowMuchUnit.Some, howMuch => howMuch);

// Register in unit converter: BaseUnit -> HowMuchUnit
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Some, HowMuchUnit.ATon, howMuch => new HowMuch(howMuch.Value * 1e-3, HowMuchUnit.ATon));
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Some, HowMuchUnit.AShitTon, howMuch => new HowMuch(howMuch.Value * 1e-6, HowMuchUnit.AShitTon));
}
}
}
40 changes: 20 additions & 20 deletions UnitsNet.Tests/QuantityInfoTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void Constructor_AssignsProperties()
var abbreviations = new ResourceManager("UnitsNet.GeneratedCode.Resources.Length", typeof(Length).Assembly);

var quantityInfo = new QuantityInfo<Length, LengthUnit>(nameof(Length), expectedBaseUnit, expectedUnitInfos, expectedZero, expectedBaseDimensions,
Length.From, abbreviations);
Length.From, Length.RegisterDefaultConversions, abbreviations);

Assert.Equal(nameof(Length), quantityInfo.Name);
Assert.Equal(typeof(Length), quantityInfo.QuantityType);
Expand All @@ -46,7 +46,7 @@ public void Constructor_AssignsProperties()
UnitInfo unitInfos = genericQuantityInfo.BaseUnitInfo;
Assert.Equal(quantityInfo.BaseUnitInfo, unitInfos);
});

// UnitInfos
Assert.Multiple(() =>
{
Expand Down Expand Up @@ -113,7 +113,7 @@ public void Constructor_AssignsPropertiesForCustomQuantity()
BaseDimensions expectedBaseDimensions = BaseDimensions.Dimensionless;

var quantityInfo = new QuantityInfo<HowMuch, HowMuchUnit>(nameof(HowMuch), expectedBaseUnit,
expectedUnitInfos, expectedZero, expectedBaseDimensions, HowMuch.From);
expectedUnitInfos, expectedZero, expectedBaseDimensions, HowMuch.From, HowMuch.RegisterUnitConversions);

Assert.Equal(nameof(HowMuch), quantityInfo.Name);
Assert.Equal(typeof(HowMuch), quantityInfo.QuantityType);
Expand Down Expand Up @@ -158,7 +158,7 @@ public void Constructor_WithoutZero_UsesZeroBaseUnit()
var expectedZero = new HowMuch(0, HowMuchUnit.Some);

var quantityInfo = new QuantityInfo<HowMuch, HowMuchUnit>(nameof(HowMuch), expectedBaseUnit,
expectedUnitInfos, expectedBaseDimensions, HowMuch.From);
expectedUnitInfos, expectedBaseDimensions, HowMuch.From, HowMuch.RegisterUnitConversions);

Assert.Equal(expectedZero, quantityInfo.Zero);
Assert.Equal(nameof(HowMuch), quantityInfo.Name);
Expand Down Expand Up @@ -186,7 +186,7 @@ public void Constructor_WithoutName_UsesDefaultQuantityTypeName()
var abbreviations = new ResourceManager("UnitsNet.GeneratedCode.Resources.Length", typeof(Length).Assembly);

var quantityInfo =
new QuantityInfo<HowMuch, HowMuchUnit>(expectedBaseUnit, expectedUnitInfos, expectedBaseDimensions, HowMuch.From, abbreviations);
new QuantityInfo<HowMuch, HowMuchUnit>(expectedBaseUnit, expectedUnitInfos, expectedBaseDimensions, HowMuch.From, HowMuch.RegisterUnitConversions, abbreviations);

Assert.Equal(expectedZero, quantityInfo.Zero);
Assert.Equal(nameof(HowMuch), quantityInfo.Name);
Expand All @@ -206,7 +206,7 @@ public void Constructor_WithoutName_UsesDefaultQuantityTypeName()
public void Constructor_GivenNullAsQuantityName_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => new QuantityInfo<Length, LengthUnit>(null!,
LengthUnit.Meter, Length.Info.UnitInfos, Length.BaseDimensions, Length.From));
LengthUnit.Meter, Length.Info.UnitInfos, Length.BaseDimensions, Length.From, Length.RegisterDefaultConversions));
}

[Fact]
Expand All @@ -215,7 +215,7 @@ public void Constructor_GivenNullAsUnitInfos_ThrowsArgumentNullException()
{
IEnumerable<UnitDefinition<LengthUnit>> nullCollection = null!;
Assert.Throws<ArgumentNullException>(() => new QuantityInfo<Length, LengthUnit>(nameof(Length),
LengthUnit.Meter, nullCollection, Length.BaseDimensions, Length.From));
LengthUnit.Meter, nullCollection, Length.BaseDimensions, Length.From, Length.RegisterDefaultConversions));
}

[Fact]
Expand All @@ -225,7 +225,7 @@ public void Constructor_GivenANullUnitInfo_ThrowsArgumentNullException()
IEnumerable<UnitDefinition<LengthUnit>> collectionContainingANull = [null!];
#if NET
Assert.Throws<ArgumentNullException>(() => new QuantityInfo<Length, LengthUnit>(nameof(Length),
LengthUnit.Meter, collectionContainingANull, Length.BaseDimensions, Length.From));
LengthUnit.Meter, collectionContainingANull, Length.BaseDimensions, Length.From, Length.RegisterDefaultConversions));
#else
Assert.Throws<NullReferenceException>(() => new QuantityInfo<Length, LengthUnit>(nameof(Length),
LengthUnit.Meter, collectionContainingANull, Length.BaseDimensions, Length.From));
Expand All @@ -237,14 +237,14 @@ public void Constructor_GivenANullUnitInfo_ThrowsArgumentNullException()
public void Constructor_GivenNullAsBaseDimensions_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => new QuantityInfo<Length, LengthUnit>(nameof(Length),
LengthUnit.Meter, Length.Info.UnitInfos, null!, Length.From));
LengthUnit.Meter, Length.Info.UnitInfos, null!, Length.From, Length.RegisterDefaultConversions));
}

[Fact]
public void Constructor_GivenAMissingBaseUnitDefinition_ThrowsUnitNotFoundException()
{
Assert.Throws<UnitNotFoundException>(() =>
new QuantityInfo<Length, LengthUnit>(Length.BaseUnit, [], Length.BaseDimensions, Length.From));
new QuantityInfo<Length, LengthUnit>(Length.BaseUnit, [], Length.BaseDimensions, Length.From, Length.RegisterDefaultConversions));
}

[Fact]
Expand Down Expand Up @@ -296,15 +296,15 @@ public void GetUnitInfoFor_GivenBaseUnitsWithMultipleMatches_ThrowsInvalidOperat
BaseDimensions dimensions = Length.BaseDimensions;
Assert.Multiple(() =>
{
var quantityInfo = new QuantityInfo<Length, LengthUnit>(LengthUnit.Meter, duplicateBaseUnits, dimensions, Length.From);
var quantityInfo = new QuantityInfo<Length, LengthUnit>(LengthUnit.Meter, duplicateBaseUnits, dimensions, Length.From, Length.RegisterDefaultConversions);
Assert.Throws<InvalidOperationException>(() => quantityInfo.GetUnitInfoFor(baseUnits));
}, () =>
{
QuantityInfo<LengthUnit> genericQuantityInfo = new QuantityInfo<Length, LengthUnit>(LengthUnit.Meter, duplicateBaseUnits, dimensions, Length.From);
QuantityInfo<LengthUnit> genericQuantityInfo = new QuantityInfo<Length, LengthUnit>(LengthUnit.Meter, duplicateBaseUnits, dimensions, Length.From, Length.RegisterDefaultConversions);
Assert.Throws<InvalidOperationException>(() => genericQuantityInfo.GetUnitInfoFor(baseUnits));
}, () =>
{
QuantityInfo genericQuantityInfo = new QuantityInfo<Length, LengthUnit>(LengthUnit.Meter, duplicateBaseUnits, dimensions, Length.From);
QuantityInfo genericQuantityInfo = new QuantityInfo<Length, LengthUnit>(LengthUnit.Meter, duplicateBaseUnits, dimensions, Length.From, Length.RegisterDefaultConversions);
Assert.Throws<InvalidOperationException>(() => genericQuantityInfo.GetUnitInfoFor(baseUnits));
});
}
Expand Down Expand Up @@ -400,7 +400,7 @@ public void GetUnitInfosFor_GivenBaseUnitsWithMultipleMatches_ReturnsMultipleMat

var quantityInfo = new QuantityInfo<Length, LengthUnit>(Length.Info.Name,
LengthUnit.Meter, new UnitDefinition<LengthUnit>[] { new(LengthUnit.Meter, "Meters", baseUnits), new(LengthUnit.Foot, "Feet", baseUnits) },
Length.BaseDimensions, Length.From);
Length.BaseDimensions, Length.From, Length.RegisterDefaultConversions);

var result = quantityInfo.GetUnitInfosFor(baseUnits).ToList();

Expand All @@ -413,9 +413,9 @@ public void GetUnitInfosFor_GivenBaseUnitsWithMultipleMatches_ReturnsMultipleMat
// public void TryGetUnitInfo_WithEnum_ReturnsTheExpectedResult()
// {
// QuantityInfo quantityInfo = HowMuch.Info;
//
//
// var success = quantityInfo.TryGetUnitInfo(HowMuchUnit.ATon, out UnitInfo? unitInfo);
//
//
// Assert.True(success);
// Assert.Equal(HowMuchUnit.ATon, unitInfo!.Value);
// }
Expand All @@ -424,9 +424,9 @@ public void GetUnitInfosFor_GivenBaseUnitsWithMultipleMatches_ReturnsMultipleMat
// public void TryGetUnitInfo_WithInvalidEnum_ReturnsTheExpectedResult()
// {
// QuantityInfo quantityInfo = HowMuch.Info;
//
//
// var success = quantityInfo.TryGetUnitInfo(LengthUnit.Meter, out UnitInfo? unitInfo);
//
//
// Assert.False(success);
// Assert.Null(unitInfo);
// }
Expand Down Expand Up @@ -625,7 +625,7 @@ public void Constructor_WithoutDelegate_UsesTheDefaultQuantityFrom()
BaseDimensions expectedBaseDimensions = BaseDimensions.Dimensionless;
var abbreviations = new ResourceManager("UnitsNet.GeneratedCode.Resources.Length", typeof(Length).Assembly);

var quantityInfo = new QuantityInfo<HowMuch, HowMuchUnit>(quantityName, expectedBaseUnit, expectedUnitInfos, expectedBaseDimensions, abbreviations);
var quantityInfo = new QuantityInfo<HowMuch, HowMuchUnit>(quantityName, expectedBaseUnit, expectedUnitInfos, expectedBaseDimensions, HowMuch.RegisterUnitConversions, abbreviations);

Assert.Equal(quantityName, quantityInfo.Name);
Assert.Equal(expectedZero, quantityInfo.Zero);
Expand All @@ -652,7 +652,7 @@ public void Constructor_WithoutNameOrDelegate_UsesTheDefaultQuantityNameAndFrom(
BaseDimensions expectedBaseDimensions = BaseDimensions.Dimensionless;
var abbreviations = new ResourceManager("UnitsNet.GeneratedCode.Resources.Length", typeof(Length).Assembly);

var quantityInfo = new QuantityInfo<HowMuch, HowMuchUnit>(expectedBaseUnit, expectedUnitInfos, expectedBaseDimensions, abbreviations);
var quantityInfo = new QuantityInfo<HowMuch, HowMuchUnit>(expectedBaseUnit, expectedUnitInfos, expectedBaseDimensions, HowMuch.RegisterUnitConversions, abbreviations);

Assert.Equal(expectedZero, quantityInfo.Zero);
Assert.Equal(nameof(HowMuch), quantityInfo.Name);
Expand Down
9 changes: 2 additions & 7 deletions UnitsNet/CustomCode/UnitsNetSetup.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using UnitsNet.Units;

namespace UnitsNet;

/// <summary>
Expand All @@ -21,10 +16,10 @@ public sealed class UnitsNetSetup
{
static UnitsNetSetup()
{
IReadOnlyCollection<QuantityInfo> quantityInfos = Quantity.DefaultProvider.Quantities;
IReadOnlyList<QuantityInfo> quantityInfos = Quantity.DefaultProvider.Quantities;

// note: in order to support the ConvertByAbbreviation, the unit converter should require a UnitParser in the constructor
var unitConverter = UnitConverter.CreateDefault();
var unitConverter = UnitConverter.Create(quantityInfos);

Default = new UnitsNetSetup(quantityInfos, unitConverter);
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions UnitsNet/GeneratedCode/Quantities/Acceleration.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions UnitsNet/GeneratedCode/Quantities/AmountOfSubstance.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading