Skip to content

Commit e5aba5b

Browse files
authored
Use UnitConverter to do conversion functions and allow basic extensibility (#1023)
1 parent 8d2eeb6 commit e5aba5b

File tree

231 files changed

+16305
-15603
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

231 files changed

+16305
-15603
lines changed

CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs

Lines changed: 68 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ private void GenerateStaticConstructor()
164164
Writer.WL($@"
165165
}},
166166
BaseUnit, Zero, BaseDimensions, QuantityType.{_quantity.Name});
167+
168+
RegisterDefaultConversions(DefaultConversionFunctions);
167169
}}
168170
");
169171
}
@@ -213,9 +215,9 @@ private void GenerateInstanceConstructors()
213215
_value = Guard.EnsureValidNumber(value, nameof(value));"
214216
: @"
215217
_value = value;");
216-
Writer.WL(@"
218+
Writer.WL($@"
217219
_unit = firstUnitInfo?.Value ?? throw new ArgumentException(""No units were found for the given UnitSystem."", nameof(unitSystem));
218-
}
220+
}}
219221
");
220222
}
221223

@@ -224,6 +226,11 @@ private void GenerateStaticProperties()
224226
Writer.WL($@"
225227
#region Static Properties
226228
229+
/// <summary>
230+
/// The <see cref=""UnitConverter"" /> containing the default generated conversion functions for <see cref=""{_quantity.Name}"" /> instances.
231+
/// </summary>
232+
public static UnitConverter DefaultConversionFunctions {{ get; }} = new UnitConverter();
233+
227234
/// <inheritdoc cref=""IQuantity.QuantityInfo""/>
228235
public static QuantityInfo<{_unitEnumName}> Info {{ get; }}
229236
@@ -360,27 +367,29 @@ internal static void RegisterDefaultConversions(UnitConverter unitConverter)
360367
if(unit.SingularName == _quantity.BaseUnit)
361368
continue;
362369

363-
Writer.WL( $@"
364-
unitConverter.SetConversionFunction<{_quantity.Name}>({_unitEnumName}.{_quantity.BaseUnit}, {_quantity.Name}Unit.{unit.SingularName}, quantity => quantity.ToUnit({_quantity.Name}Unit.{unit.SingularName}));");
370+
var func = unit.FromBaseToUnitFunc.Replace("{x}", "quantity.Value");
371+
Writer.WL($@"
372+
unitConverter.SetConversionFunction<{_quantity.Name}>({_unitEnumName}.{_quantity.BaseUnit}, {_quantity.Name}Unit.{unit.SingularName}, quantity => new {_quantity.Name}({func}, {_quantity.Name}Unit.{unit.SingularName}));");
365373
}
366374

367-
Writer.WL( $@"
375+
Writer.WL($@"
368376
369377
// Register in unit converter: BaseUnit <-> BaseUnit
370378
unitConverter.SetConversionFunction<{_quantity.Name}>({_unitEnumName}.{_quantity.BaseUnit}, {_unitEnumName}.{_quantity.BaseUnit}, quantity => quantity);
371379
372-
// Register in unit converter: {_quantity.Name}Unit -> BaseUnit" );
380+
// Register in unit converter: {_quantity.Name}Unit -> BaseUnit");
373381

374382
foreach(var unit in _quantity.Units)
375383
{
376384
if(unit.SingularName == _quantity.BaseUnit)
377385
continue;
378386

379-
Writer.WL($@"
380-
unitConverter.SetConversionFunction<{_quantity.Name}>({_quantity.Name}Unit.{unit.SingularName}, {_unitEnumName}.{_quantity.BaseUnit}, quantity => quantity.ToBaseUnit());" );
387+
var func = unit.FromUnitToBaseFunc.Replace("{x}", "quantity.Value");
388+
Writer.WL($@"
389+
unitConverter.SetConversionFunction<{_quantity.Name}>({_quantity.Name}Unit.{unit.SingularName}, {_unitEnumName}.{_quantity.BaseUnit}, quantity => new {_quantity.Name}({func}, {_unitEnumName}.{_quantity.BaseUnit}));");
381390
}
382391

383-
Writer.WL( $@"
392+
Writer.WL($@"
384393
}}
385394
386395
/// <summary>
@@ -903,11 +912,42 @@ double IQuantity.As(Enum unit)
903912
/// <summary>
904913
/// Converts this {_quantity.Name} to another {_quantity.Name} with the unit representation <paramref name=""unit"" />.
905914
/// </summary>
915+
/// <param name=""unit"">The unit to convert to.</param>
906916
/// <returns>A {_quantity.Name} with the specified unit.</returns>
907917
public {_quantity.Name} ToUnit({_unitEnumName} unit)
908918
{{
909-
var convertedValue = GetValueAs(unit);
910-
return new {_quantity.Name}(convertedValue, unit);
919+
return ToUnit(unit, DefaultConversionFunctions);
920+
}}
921+
922+
/// <summary>
923+
/// Converts this {_quantity.Name} to another {_quantity.Name} using the given <paramref name=""unitConverter""/> with the unit representation <paramref name=""unit"" />.
924+
/// </summary>
925+
/// <param name=""unit"">The unit to convert to.</param>
926+
/// <param name=""unitConverter"">The <see cref=""UnitConverter""/> to use for the conversion.</param>
927+
/// <returns>A {_quantity.Name} with the specified unit.</returns>
928+
public {_quantity.Name} ToUnit({_unitEnumName} unit, UnitConverter unitConverter)
929+
{{
930+
if(Unit == unit)
931+
{{
932+
// Already in requested units.
933+
return this;
934+
}}
935+
else if(unitConverter.TryGetConversionFunction((typeof({_quantity.Name}), Unit, typeof({_quantity.Name}), unit), out var conversionFunction))
936+
{{
937+
// Direct conversion to requested unit found. Return the converted quantity.
938+
var converted = conversionFunction(this);
939+
return ({_quantity.Name})converted;
940+
}}
941+
else if(Unit != BaseUnit)
942+
{{
943+
// Direct conversion to requested unit NOT found. Convert to BaseUnit, and then from BaseUnit to requested unit.
944+
var inBaseUnits = ToUnit(BaseUnit);
945+
return inBaseUnits.ToUnit(unit);
946+
}}
947+
else
948+
{{
949+
throw new NotImplementedException($""Can not convert {{Unit}} to {{unit}}."");
950+
}}
911951
}}
912952
913953
/// <inheritdoc />
@@ -916,7 +956,16 @@ IQuantity IQuantity.ToUnit(Enum unit)
916956
if(!(unit is {_unitEnumName} unitAs{_unitEnumName}))
917957
throw new ArgumentException($""The given unit is of type {{unit.GetType()}}. Only {{typeof({_unitEnumName})}} is supported."", nameof(unit));
918958
919-
return ToUnit(unitAs{_unitEnumName});
959+
return ToUnit(unitAs{_unitEnumName}, DefaultConversionFunctions);
960+
}}
961+
962+
/// <inheritdoc />
963+
IQuantity IQuantity.ToUnit(Enum unit, UnitConverter unitConverter)
964+
{{
965+
if(!(unit is {_unitEnumName} unitAs{_unitEnumName}))
966+
throw new ArgumentException($""The given unit is of type {{unit.GetType()}}. Only {{typeof({_unitEnumName})}} is supported."", nameof(unit));
967+
968+
return ToUnit(unitAs{_unitEnumName}, unitConverter);
920969
}}
921970
922971
/// <inheritdoc cref=""IQuantity.ToUnit(UnitSystem)""/>
@@ -941,62 +990,16 @@ IQuantity IQuantity.ToUnit(Enum unit)
941990
IQuantity<{_unitEnumName}> IQuantity<{_unitEnumName}>.ToUnit({_unitEnumName} unit) => ToUnit(unit);
942991
943992
/// <inheritdoc />
944-
IQuantity<{_unitEnumName}> IQuantity<{_unitEnumName}>.ToUnit(UnitSystem unitSystem) => ToUnit(unitSystem);
993+
IQuantity<{_unitEnumName}> IQuantity<{_unitEnumName}>.ToUnit({_unitEnumName} unit, UnitConverter unitConverter) => ToUnit(unit, unitConverter);
945994
946-
/// <summary>
947-
/// Converts the current value + unit to the base unit.
948-
/// This is typically the first step in converting from one unit to another.
949-
/// </summary>
950-
/// <returns>The value in the base unit representation.</returns>
951-
private {_valueType} GetValueInBaseUnit()
952-
{{
953-
switch(Unit)
954-
{{");
955-
foreach (var unit in _quantity.Units)
956-
{
957-
var func = unit.FromUnitToBaseFunc.Replace("{x}", "_value");
958-
Writer.WL($@"
959-
case {_unitEnumName}.{unit.SingularName}: return {func};");
960-
}
961-
962-
Writer.WL($@"
963-
default:
964-
throw new NotImplementedException($""Can not convert {{Unit}} to base units."");
965-
}}
966-
}}
967-
968-
/// <summary>
969-
/// Converts the current value + unit to the base unit.
970-
/// This is typically the first step in converting from one unit to another.
971-
/// </summary>
972-
/// <returns>The value in the base unit representation.</returns>
973-
internal {_quantity.Name} ToBaseUnit()
974-
{{
975-
var baseUnitValue = GetValueInBaseUnit();
976-
return new {_quantity.Name}(baseUnitValue, BaseUnit);
977-
}}
995+
/// <inheritdoc />
996+
IQuantity<{_unitEnumName}> IQuantity<{_unitEnumName}>.ToUnit(UnitSystem unitSystem) => ToUnit(unitSystem);
978997
979998
private {_valueType} GetValueAs({_unitEnumName} unit)
980999
{{
981-
if(Unit == unit)
982-
return _value;
983-
984-
var baseUnitValue = GetValueInBaseUnit();
985-
986-
switch(unit)
987-
{{");
988-
foreach (var unit in _quantity.Units)
989-
{
990-
var func = unit.FromBaseToUnitFunc.Replace("{x}", "baseUnitValue");
991-
Writer.WL($@"
992-
case {_unitEnumName}.{unit.SingularName}: return {func};");
993-
}
994-
995-
Writer.WL(@"
996-
default:
997-
throw new NotImplementedException($""Can not convert {Unit} to {unit}."");
998-
}
999-
}
1000+
var converted = ToUnit(unit);
1001+
return ({_valueType})converted.Value;
1002+
}}
10001003
10011004
#endregion
10021005
");
@@ -1084,7 +1087,7 @@ public string ToString(string format, IFormatProvider? provider)
10841087
}}
10851088
10861089
#endregion
1087-
" );
1090+
");
10881091
}
10891092

10901093
private void GenerateIConvertibleMethods()

CodeGen/Generators/UnitsNetGen/UnitTestBaseClassGenerator.cs

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public override string Generate()
7676
Writer.WL(GeneratedFileHeader);
7777
Writer.WL($@"
7878
using System;
79+
using System.Collections.Generic;
7980
using System.Globalization;
8081
using System.Linq;
8182
using System.Threading;
@@ -95,16 +96,37 @@ namespace UnitsNet.Tests
9596
// ReSharper disable once PartialTypeWithSinglePart
9697
public abstract partial class {_quantity.Name}TestsBase : QuantityTestsBase
9798
{{");
98-
foreach (var unit in _quantity.Units) Writer.WL($@"
99+
foreach(var unit in _quantity.Units) Writer.WL($@"
99100
protected abstract double {unit.PluralName}InOne{_baseUnit.SingularName} {{ get; }}");
100101

101102
Writer.WL("");
102103
Writer.WL($@"
103104
// ReSharper disable VirtualMemberNeverOverriden.Global");
104-
foreach (var unit in _quantity.Units) Writer.WL($@"
105+
foreach(var unit in _quantity.Units) Writer.WL($@"
105106
protected virtual double {unit.PluralName}Tolerance {{ get {{ return 1e-5; }} }}"); Writer.WL($@"
106107
// ReSharper restore VirtualMemberNeverOverriden.Global
107108
109+
protected (double UnitsInBaseUnit, double Tolerence) GetConversionFactor({_unitEnumName} unit)
110+
{{
111+
return unit switch
112+
{{");
113+
foreach(var unit in _quantity.Units) Writer.WL($@"
114+
{GetUnitFullName(unit)} => ({unit.PluralName}InOne{_baseUnit.SingularName}, {unit.PluralName}Tolerance),");
115+
Writer.WL($@"
116+
_ => throw new NotSupportedException()
117+
}};
118+
}}
119+
120+
public static IEnumerable<object[]> UnitTypes = new List<object[]>
121+
{{");
122+
foreach(var unit in _quantity.Units)
123+
{
124+
Writer.WL($@"
125+
new object[] {{ {GetUnitFullName(unit)} }},");
126+
}
127+
Writer.WL($@"
128+
}};
129+
108130
[Fact]
109131
public void Ctor_WithUndefinedUnit_ThrowsArgumentException()
110132
{{
@@ -116,14 +138,14 @@ public void DefaultCtor_ReturnsQuantityWithZeroValueAndBaseUnit()
116138
{{
117139
var quantity = new {_quantity.Name}();
118140
Assert.Equal(0, quantity.Value);");
119-
if (_quantity.BaseType == "decimal") Writer.WL($@"
141+
if(_quantity.BaseType == "decimal") Writer.WL($@"
120142
Assert.Equal(0m, ((IDecimalQuantity)quantity).Value);");
121143
Writer.WL($@"
122144
Assert.Equal({_baseUnitFullName}, quantity.Unit);
123145
}}
124146
125147
");
126-
if (_quantity.BaseType == "double") Writer.WL($@"
148+
if(_quantity.BaseType == "double") Writer.WL($@"
127149
[Fact]
128150
public void Ctor_WithInfinityValue_ThrowsArgumentException()
129151
{{
@@ -183,7 +205,7 @@ public void Ctor_SIUnitSystem_ThrowsArgumentExceptionIfNotSupported()
183205
{{
184206
{_quantity.Name} {baseUnitVariableName} = {_quantity.Name}.From{_baseUnit.PluralName}(1);");
185207

186-
foreach (var unit in _quantity.Units) Writer.WL($@"
208+
foreach(var unit in _quantity.Units) Writer.WL($@"
187209
AssertEx.EqualTolerance({unit.PluralName}InOne{_baseUnit.SingularName}, {baseUnitVariableName}.{unit.PluralName}, {unit.PluralName}Tolerance);");
188210
Writer.WL($@"
189211
}}
@@ -192,7 +214,7 @@ public void Ctor_SIUnitSystem_ThrowsArgumentExceptionIfNotSupported()
192214
public void From_ValueAndUnit_ReturnsQuantityWithSameValueAndUnit()
193215
{{");
194216
int i = 0;
195-
foreach (var unit in _quantity.Units)
217+
foreach(var unit in _quantity.Units)
196218
{
197219
var quantityVariable = $"quantity{i++:D2}";
198220
Writer.WL($@"
@@ -205,7 +227,7 @@ public void From_ValueAndUnit_ReturnsQuantityWithSameValueAndUnit()
205227
Writer.WL($@"
206228
}}
207229
");
208-
if (_quantity.BaseType == "double") Writer.WL($@"
230+
if(_quantity.BaseType == "double") Writer.WL($@"
209231
[Fact]
210232
public void From{_baseUnit.PluralName}_WithInfinityValue_ThrowsArgumentException()
211233
{{
@@ -224,7 +246,7 @@ public void From_ValueAndUnit_ReturnsQuantityWithSameValueAndUnit()
224246
public void As()
225247
{{
226248
var {baseUnitVariableName} = {_quantity.Name}.From{_baseUnit.PluralName}(1);");
227-
foreach (var unit in _quantity.Units) Writer.WL($@"
249+
foreach(var unit in _quantity.Units) Writer.WL($@"
228250
AssertEx.EqualTolerance({unit.PluralName}InOne{_baseUnit.SingularName}, {baseUnitVariableName}.As({GetUnitFullName(unit)}), {unit.PluralName}Tolerance);");
229251
Writer.WL($@"
230252
}}
@@ -246,29 +268,41 @@ public void As_SIUnitSystem_ThrowsArgumentExceptionIfNotSupported()
246268
}}
247269
}}
248270
249-
[Fact]
250-
public void ToUnit()
271+
[Theory]
272+
[MemberData(nameof(UnitTypes))]
273+
public void ToUnit({_unitEnumName} unit)
251274
{{
252-
var {baseUnitVariableName} = {_quantity.Name}.From{_baseUnit.PluralName}(1);");
253-
foreach (var unit in _quantity.Units)
254-
{
255-
var asQuantityVariableName = $"{unit.SingularName.ToLowerInvariant()}Quantity";
275+
var inBaseUnits = {_quantity.Name}.From(1.0, {_quantity.Name}.BaseUnit);
276+
var converted = inBaseUnits.ToUnit(unit);
256277
257-
Writer.WL("");
258-
Writer.WL($@"
259-
var {asQuantityVariableName} = {baseUnitVariableName}.ToUnit({GetUnitFullName(unit)});
260-
AssertEx.EqualTolerance({unit.PluralName}InOne{_baseUnit.SingularName}, (double){asQuantityVariableName}.Value, {unit.PluralName}Tolerance);
261-
Assert.Equal({GetUnitFullName(unit)}, {asQuantityVariableName}.Unit);");
262-
}
263-
Writer.WL($@"
278+
var conversionFactor = GetConversionFactor(unit);
279+
AssertEx.EqualTolerance(conversionFactor.UnitsInBaseUnit, (double)converted.Value, conversionFactor.Tolerence);
280+
Assert.Equal(unit, converted.Unit);
264281
}}
265282
266-
[Fact]
267-
public void ToBaseUnit_ReturnsQuantityWithBaseUnit()
283+
[Theory]
284+
[MemberData(nameof(UnitTypes))]
285+
public void ToUnit_WithSameUnits_AreEqual({_unitEnumName} unit)
268286
{{
269-
var quantityInBaseUnit = {_quantity.Name}.From{_baseUnit.PluralName}(1).ToBaseUnit();
270-
Assert.Equal({_quantity.Name}.BaseUnit, quantityInBaseUnit.Unit);");
271-
Writer.WL($@"
287+
var quantity = {_quantity.Name}.From(3.0, unit);
288+
var toUnitWithSameUnit = quantity.ToUnit(unit);
289+
Assert.Equal(quantity, toUnitWithSameUnit);
290+
}}
291+
292+
[Theory]
293+
[MemberData(nameof(UnitTypes))]
294+
public void ToUnit_FromNonBaseUnit_ReturnsQuantityWithGivenUnit({_unitEnumName} unit)
295+
{{
296+
// See if there is a unit available that is not the base unit.
297+
var fromUnit = {_quantity.Name}.Units.FirstOrDefault(u => u != {_quantity.Name}.BaseUnit && u != {_unitEnumName}.Undefined);
298+
299+
// If there is only one unit for the quantity, we must use the base unit.
300+
if(fromUnit == {_unitEnumName}.Undefined)
301+
fromUnit = {_quantity.Name}.BaseUnit;
302+
303+
var quantity = {_quantity.Name}.From(3.0, fromUnit);
304+
var converted = quantity.ToUnit(unit);
305+
Assert.Equal(converted.Unit, unit);
272306
}}
273307
274308
[Fact]
@@ -701,9 +735,9 @@ public void GetHashCode_Equals()
701735
}}
702736
");
703737

704-
if( _quantity.GenerateArithmetic )
738+
if(_quantity.GenerateArithmetic)
705739
{
706-
Writer.WL( $@"
740+
Writer.WL($@"
707741
[Theory]
708742
[InlineData(1.0)]
709743
[InlineData(-1.0)]
@@ -716,7 +750,7 @@ public void NegationOperator_ReturnsQuantity_WithNegatedValue(double value)
716750

717751
Writer.WL($@"
718752
}}
719-
}}" );
753+
}}");
720754
return Writer.ToString();
721755
}
722756
}

0 commit comments

Comments
 (0)