Skip to content

QuantityFormatter: Take UnitAbbreviationsCache instance, Format with TQuantity #1551

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

Merged
merged 7 commits into from
Apr 19, 2025
Merged
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
30 changes: 16 additions & 14 deletions CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,25 +290,30 @@ private void GenerateProperties()
/// </summary>
public double Value => _value;

/// <inheritdoc />
double IQuantity.Value => _value;

Enum IQuantity.Unit => Unit;

/// <inheritdoc />
public {_unitEnumName} Unit => _unit.GetValueOrDefault(BaseUnit);

/// <inheritdoc />
public QuantityInfo<{_unitEnumName}> QuantityInfo => Info;

/// <inheritdoc cref=""IQuantity.QuantityInfo""/>
QuantityInfo IQuantity.QuantityInfo => Info;

/// <summary>
/// The <see cref=""BaseDimensions"" /> of this quantity.
/// </summary>
public BaseDimensions Dimensions => {_quantity.Name}.BaseDimensions;

#region Explicit implementations

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

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

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
QuantityInfo IQuantity.QuantityInfo => Info;

#endregion

#endregion
");
}
Expand Down Expand Up @@ -1167,7 +1172,7 @@ public string ToString(IFormatProvider? provider)
return ToString(null, provider);
}}

/// <inheritdoc cref=""QuantityFormatter.Format{{TUnitType}}(IQuantity{{TUnitType}}, string, IFormatProvider)""/>
/// <inheritdoc cref=""QuantityFormatter.Format{{TQuantity}}(TQuantity, string?, IFormatProvider?)""/>
/// <summary>
/// Gets the string representation of this instance in the specified format string using <see cref=""CultureInfo.CurrentCulture"" />.
/// </summary>
Expand All @@ -1178,16 +1183,13 @@ public string ToString(string? format)
return ToString(format, null);
}}

/// <inheritdoc cref=""QuantityFormatter.Format{{TUnitType}}(IQuantity{{TUnitType}}, string, IFormatProvider)""/>
/// <inheritdoc cref=""QuantityFormatter.Format{{TQuantity}}(TQuantity, string?, IFormatProvider?)""/>
/// <summary>
/// Gets the string representation of this instance in the specified format string using the specified format provider, or <see cref=""CultureInfo.CurrentCulture"" /> if null.
/// </summary>
/// <param name=""format"">The format string.</param>
/// <param name=""provider"">Format to use for localization and number formatting. Defaults to <see cref=""CultureInfo.CurrentCulture"" /> if null.</param>
/// <returns>The string representation.</returns>
public string ToString(string? format, IFormatProvider? provider)
{{
return QuantityFormatter.Format<{_unitEnumName}>(this, format, provider);
return QuantityFormatter.Default.Format(this, format, provider);
}}

#endregion
Expand Down
Copy link
Collaborator Author

@lipchev lipchev Apr 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The benchmark results are out, but first a note on the size:

  • before: 2.18 MB (2 294 784 bytes)
  • after: 2.19 MB (2 301 440 bytes)

Before (NET8):

Method Job Runtime NbConversions Format Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
MassToString .NET 8.0 .NET 8.0 1000 A 25.87 μs 0.475 μs 0.444 μs 1.00 0.02 1.8921 31.25 KB 1.00
VolumeFlowToString .NET 8.0 .NET 8.0 1000 A 26.10 μs 0.300 μs 0.250 μs 1.01 0.02 1.8921 31.25 KB 1.00
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 A 97.03 μs 0.278 μs 0.247 μs 1.00 0.00 10.1318 62.68 KB 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 A 97.87 μs 0.735 μs 0.688 μs 1.01 0.01 10.1318 62.68 KB 1.00
MassToString .NET 8.0 .NET 8.0 1000 E 132.45 μs 0.638 μs 0.597 μs 1.00 0.01 9.0332 149.52 KB 1.00
VolumeFlowToString .NET 8.0 .NET 8.0 1000 E 135.59 μs 0.611 μs 0.510 μs 1.02 0.01 9.5215 157.73 KB 1.05
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 E 503.20 μs 3.910 μs 3.657 μs 1.00 0.01 61.5234 383.86 KB 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 E 513.89 μs 4.447 μs 4.160 μs 1.02 0.01 62.5000 388.93 KB 1.01
MassToString .NET 8.0 .NET 8.0 1000 G 132.35 μs 0.903 μs 0.845 μs 1.00 0.01 8.5449 140.54 KB 1.00
VolumeFlowToString .NET 8.0 .NET 8.0 1000 G 139.97 μs 0.765 μs 0.678 μs 1.06 0.01 8.7891 145.59 KB 1.04
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 G 492.04 μs 1.356 μs 1.059 μs 1.00 0.00 57.1289 353.68 KB 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 G 498.79 μs 4.060 μs 3.798 μs 1.01 0.01 58.5938 361.93 KB 1.02
MassToString .NET 8.0 .NET 8.0 1000 N 143.85 μs 0.263 μs 0.233 μs 1.00 0.00 8.3008 135.62 KB 1.00
VolumeFlowToString .NET 8.0 .NET 8.0 1000 N 147.99 μs 0.259 μs 0.242 μs 1.03 0.00 8.5449 143.47 KB 1.06
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 N 513.30 μs 1.213 μs 1.075 μs 1.00 0.00 56.6406 353.35 KB 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 N 501.80 μs 3.087 μs 2.887 μs 0.98 0.01 57.6172 358.88 KB 1.02
MassToString .NET 8.0 .NET 8.0 1000 S 218.76 μs 1.542 μs 1.443 μs 1.00 0.01 20.0195 328.04 KB 1.00
VolumeFlowToString .NET 8.0 .NET 8.0 1000 S 219.61 μs 0.730 μs 0.647 μs 1.00 0.01 20.2637 333.09 KB 1.02
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 S 690.58 μs 2.153 μs 1.909 μs 1.00 0.00 95.7031 588.75 KB 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 S 662.66 μs 3.150 μs 2.947 μs 0.96 0.00 96.6797 596.99 KB 1.01

Before (NET9)

Method Job Runtime NbConversions Format Mean Error StdDev Ratio Gen0 Allocated Alloc Ratio
MassToString .NET 9.0 .NET 9.0 1000 A 31.94 μs 0.109 μs 0.102 μs 1.00 1.8921 31.25 KB 1.00
VolumeFlowToString .NET 9.0 .NET 9.0 1000 A 33.19 μs 0.200 μs 0.187 μs 1.04 1.8921 31.25 KB 1.00
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 A 92.59 μs 0.553 μs 0.517 μs 1.00 10.1318 62.68 KB 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 A 93.89 μs 0.719 μs 0.638 μs 1.01 10.1318 62.68 KB 1.00
MassToString .NET 9.0 .NET 9.0 1000 E 130.05 μs 0.100 μs 0.078 μs 1.00 9.0332 149.52 KB 1.00
VolumeFlowToString .NET 9.0 .NET 9.0 1000 E 133.25 μs 0.665 μs 0.622 μs 1.02 9.5215 157.73 KB 1.05
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 E 507.10 μs 3.242 μs 2.874 μs 1.00 61.5234 383.86 KB 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 E 499.04 μs 1.143 μs 1.070 μs 0.98 62.5000 388.93 KB 1.01
MassToString .NET 9.0 .NET 9.0 1000 G 128.11 μs 0.424 μs 0.397 μs 1.00 8.5449 140.54 KB 1.00
VolumeFlowToString .NET 9.0 .NET 9.0 1000 G 131.94 μs 0.567 μs 0.531 μs 1.03 8.7891 145.59 KB 1.04
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 G 487.63 μs 1.922 μs 1.798 μs 1.00 56.6406 353.68 KB 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 G 490.62 μs 2.421 μs 2.147 μs 1.01 58.5938 361.93 KB 1.02
MassToString .NET 9.0 .NET 9.0 1000 N 143.22 μs 0.812 μs 0.759 μs 1.00 8.3008 135.62 KB 1.00
VolumeFlowToString .NET 9.0 .NET 9.0 1000 N 144.14 μs 0.508 μs 0.450 μs 1.01 8.5449 143.47 KB 1.06
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 N 493.37 μs 1.085 μs 0.961 μs 1.00 56.6406 353.35 KB 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 N 494.64 μs 2.060 μs 1.826 μs 1.00 57.6172 358.88 KB 1.02
MassToString .NET 9.0 .NET 9.0 1000 S 161.52 μs 0.955 μs 0.893 μs 1.00 13.1836 218.66 KB 1.00
VolumeFlowToString .NET 9.0 .NET 9.0 1000 S 162.04 μs 0.825 μs 0.731 μs 1.00 13.6719 223.72 KB 1.02
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 S 657.28 μs 4.616 μs 4.318 μs 1.00 95.7031 588.75 KB 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 S 693.80 μs 6.590 μs 5.842 μs 1.06 96.6797 596.99 KB 1.01

After:

Method Job Runtime NbConversions Format Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio
MassToString .NET 9.0 .NET 9.0 1000 A 24.42 μs 0.062 μs 0.055 μs 24.42 μs 1.00 0.00 - - NA
VolumeFlowToString .NET 9.0 .NET 9.0 1000 A 26.33 μs 0.009 μs 0.009 μs 26.33 μs 1.08 0.00 - - NA
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 A 92.61 μs 0.223 μs 0.209 μs 92.61 μs 1.00 0.00 5.0049 32094 B 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 A 87.29 μs 0.058 μs 0.054 μs 87.29 μs 0.94 0.00 5.0049 32094 B 1.00
MassToString .NET 9.0 .NET 9.0 1000 E 101.32 μs 0.165 μs 0.146 μs 101.34 μs 1.00 0.00 6.2256 105104 B 1.00
VolumeFlowToString .NET 9.0 .NET 9.0 1000 E 105.06 μs 0.555 μs 0.463 μs 105.26 μs 1.04 0.00 6.7139 113520 B 1.08
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 E 399.10 μs 0.768 μs 0.681 μs 399.22 μs 1.00 0.00 23.9258 152362 B 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 E 394.08 μs 0.313 μs 0.244 μs 394.11 μs 0.99 0.00 24.9023 157554 B 1.03
MassToString .NET 9.0 .NET 9.0 1000 G 101.35 μs 0.199 μs 0.176 μs 101.27 μs 1.00 0.00 5.2490 87912 B 1.00
VolumeFlowToString .NET 9.0 .NET 9.0 1000 G 106.90 μs 0.240 μs 0.201 μs 106.90 μs 1.05 0.00 5.4932 93088 B 1.06
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 G 391.88 μs 2.477 μs 2.196 μs 391.12 μs 1.00 0.01 19.0430 121461 B 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 G 388.93 μs 1.169 μs 0.976 μs 388.80 μs 0.99 0.01 20.5078 129901 B 1.07
MassToString .NET 9.0 .NET 9.0 1000 N 117.68 μs 2.083 μs 3.480 μs 116.08 μs 1.00 0.04 4.8828 82872 B 1.00
VolumeFlowToString .NET 9.0 .NET 9.0 1000 N 117.19 μs 0.224 μs 0.187 μs 117.21 μs 1.00 0.03 5.3711 90912 B 1.10
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 N 395.04 μs 2.405 μs 2.132 μs 394.57 μs 1.00 0.01 19.0430 121117 B 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 N 385.76 μs 0.515 μs 0.430 μs 385.65 μs 0.98 0.01 20.0195 126781 B 1.05
MassToString .NET 9.0 .NET 9.0 1000 S 151.99 μs 0.379 μs 0.336 μs 152.03 μs 1.00 0.00 11.2305 191912 B 1.00
VolumeFlowToString .NET 9.0 .NET 9.0 1000 S 156.02 μs 0.202 μs 0.189 μs 156.01 μs 1.03 0.00 11.7188 197088 B 1.03
MassToString .NET Framework 4.8 .NET Framework 4.8 1000 S 650.07 μs 3.216 μs 3.008 μs 649.23 μs 1.00 0.01 89.8438 570782 B 1.00
VolumeFlowToString .NET Framework 4.8 .NET Framework 4.8 1000 S 675.46 μs 3.121 μs 2.606 μs 674.95 μs 1.04 0.01 91.7969 579223 B 1.01

PS Shit, forgot to switch the framework target for the before (it's NET8).. anyway, you see the lack of allocations on the A format.
PS2 I've updated the results, and interestingly NET9 (before) performed slightly worse on the A format..

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace UnitsNet.Benchmark.Conversions.ToString;

[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net48)]
[SimpleJob(RuntimeMoniker.Net80)]
[SimpleJob(RuntimeMoniker.Net90)]
public class ToStringWithDefaultPrecisionBenchmarks
{
private static readonly double Value = 123.456;
Expand Down
8 changes: 4 additions & 4 deletions UnitsNet.Benchmark/Enums/UnitKeyEqualsBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ public class UnitKeyEqualsBenchmarks

private static readonly UnitKey UnitKey = UnitKey.ForUnit(VolumeUnit.CubicMeter);
private static readonly UnitKey OtherUnitKey = UnitKey.ForUnit(VolumeUnit.AcreFoot);
private readonly Type OtherUnitType = UnitKey.UnitType;
private readonly int OtherUnitValue = UnitKey.UnitValue;
private readonly Type OtherUnitType = UnitKey.UnitEnumType;
private readonly int OtherUnitValue = UnitKey.UnitEnumValue;

private readonly Type UnitType = UnitKey.UnitType;
private readonly int UnitValue = UnitKey.UnitValue;
private readonly Type UnitType = UnitKey.UnitEnumType;
private readonly int UnitValue = UnitKey.UnitEnumValue;

[Benchmark(Baseline = true)]
public bool EqualsRecord()
Expand Down
4 changes: 2 additions & 2 deletions UnitsNet.Benchmark/Enums/UnitKeyHashCodeBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public class UnitKeyHashCodeBenchmarks

private static readonly UnitKey UnitKey = UnitKey.ForUnit(VolumeUnit.CubicMeter);

private readonly Type UnitType = UnitKey.UnitType;
private readonly int UnitValue = UnitKey.UnitValue;
private readonly Type UnitType = UnitKey.UnitEnumType;
private readonly int UnitValue = UnitKey.UnitEnumValue;

[Benchmark(Baseline = true)]
public int GetHashCodeRecord()
Expand Down
2 changes: 1 addition & 1 deletion UnitsNet.Benchmark/Enums/UnitKeyToEnumBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public int ManualCast()
var total = 0;
for (var i = 0; i < NbIterations; i++)
{
if ((MassUnit)unitKey.UnitValue == MassUnit.Gram)
if ((MassUnit)unitKey.UnitEnumValue == MassUnit.Gram)
{
total++;
}
Expand Down
5 changes: 5 additions & 0 deletions UnitsNet.Tests/CustomQuantities/HowMuch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ QuantityInfo IQuantity.QuantityInfo
{
get { return Info; }
}

public UnitKey UnitKey
{
get => UnitKey.ForUnit(Unit);
}

public double As(Enum unit) => Convert.ToDouble(unit);

Expand Down
34 changes: 0 additions & 34 deletions UnitsNet.Tests/DummyIQuantity.cs

This file was deleted.

31 changes: 26 additions & 5 deletions UnitsNet.Tests/QuantityFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Globalization;
using UnitsNet.Tests.Helpers;
using Xunit;

namespace UnitsNet.Tests
Expand Down Expand Up @@ -95,9 +96,9 @@ public static void StandardNumericFormatStrings_Equals_ValueWithFormatStringAndA
var length = Length.FromMeters(123456789.987654321);

var expected = string.Format(CultureInfo.CurrentCulture, $"{{0:{format}}} {{1:a}}", length.Value, length);
Assert.Equal(expected, QuantityFormatter.Format(length, format));
Assert.Equal(expected, QuantityFormatter.Default.Format(length, format));
}

[Theory]
[InlineData("U")]
[InlineData("u")]
Expand Down Expand Up @@ -140,9 +141,9 @@ public static void StandardNumericFormatStrings_Equals_ValueWithFormatStringAndA
public static void Format_WithUnsupportedFormatString_ThrowsFormatException(string format)
{
var length = Length.FromMeters(123456789.987654321);
Assert.Throws<FormatException>(() => QuantityFormatter.Format(length, format));
Assert.Throws<FormatException>(() => QuantityFormatter.Default.Format(length, format));
}

[Fact]
public static void StandardNumericFormatStringsAsPartOfLongerFormatStringsWork()
{
Expand Down Expand Up @@ -173,7 +174,27 @@ public static void CustomNumericFormatStrings_Equals_ValueWithFormatStringAndAbb
var length = Length.FromMeters(123456789.987654321);

var expected = string.Format(CultureInfo.CurrentCulture, $"{{0:{format}}} {{1:a}}", length.Value, length);
Assert.Equal(expected, QuantityFormatter.Format(length, format));
Assert.Equal(expected, QuantityFormatter.Default.Format(length, format));
}

[Fact]
public void Format_WithoutFormatParameter_FormatsWithGeneralFormatWithCurrentCulture()
{
using var cultureScope = new CultureScope(CultureInfo.InvariantCulture);
var length = Length.FromMeters(123.321);
var expected = "123.321 m";
var actual = QuantityFormatter.Default.Format(length);
Assert.Equal(expected, actual);
}

[Fact]
public void Format_WithFormatParameter_FormatsWithCurrentCulture()
{
using var cultureScope = new CultureScope(CultureInfo.InvariantCulture);
var length = Length.FromMeters(123.321);
var expected = "123.321 m";
var actual = QuantityFormatter.Format(length, "G");
Assert.Equal(expected, actual);
}
}
}
2 changes: 1 addition & 1 deletion UnitsNet.Tests/QuantityTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public void TryFrom_GivenValueAndUnit_ReturnsQuantity()
[Fact]
public void TryParse_GivenInvalidQuantityType_ReturnsFalseAndNullQuantity()
{
Assert.False(Quantity.TryParse(InvariantCulture, typeof(DummyIQuantity), "3.0 cm", out IQuantity? parsedLength));
Assert.False(Quantity.TryParse(InvariantCulture, typeof(DateTime), "3.0 cm", out IQuantity? parsedLength));
Assert.Null(parsedLength);
}

Expand Down
12 changes: 12 additions & 0 deletions UnitsNet.Tests/UnitAbbreviationsCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ public void GetDefaultAbbreviationFallsBackToInvariantCulture()
// Assert
Assert.Equal("Invariant abbreviation for Unit1", abbreviation);
}

[Fact]
public void GetDefaultAbbreviation_WithNullUnitType_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => UnitAbbreviationsCache.Default.GetDefaultAbbreviation(null!, 1));
}

[Fact]
public void GetDefaultAbbreviationThrowsUnitNotFoundExceptionIfNoneExist()
Expand All @@ -149,6 +155,12 @@ public void GetAllUnitAbbreviationsForQuantity_WithQuantityWithoutAbbreviations_
Assert.Empty(unitAbbreviationsCache.GetAllUnitAbbreviationsForQuantity(typeof(HowMuchUnit)));
}

[Fact]
public void GetAllUnitAbbreviationsForQuantity_WithNullUnitType_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => UnitAbbreviationsCache.Default.GetAllUnitAbbreviationsForQuantity(null!));
}

[Fact]
public void GetAllUnitAbbreviationsForQuantity_WithInvalidUnitType_ThrowsArgumentException()
{
Expand Down
51 changes: 37 additions & 14 deletions UnitsNet.Tests/UnitKeyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ public enum TestUnit
public void Constructor_ShouldCreateUnitKey(int unitValue)
{
var unitKey = new UnitKey(typeof(TestUnit), unitValue);
Assert.Equal(typeof(TestUnit), unitKey.UnitType);
Assert.Equal(unitValue, unitKey.UnitValue);
Assert.Equal(typeof(TestUnit), unitKey.UnitEnumType);
Assert.Equal(unitValue, unitKey.UnitEnumValue);
}

[Fact]
public void Constructor_WithNullType_ShouldNotThrow()
{
var unitKey = new UnitKey(null!, 0);
Assert.Null(unitKey.UnitType);
Assert.Equal(0, unitKey.UnitValue);
Assert.Null(unitKey.UnitEnumType);
Assert.Equal(0, unitKey.UnitEnumValue);
}

[Theory]
Expand All @@ -42,8 +42,8 @@ public void Constructor_WithNullType_ShouldNotThrow()
public void ForUnit_ShouldCreateUnitKey(TestUnit unit)
{
var unitKey = UnitKey.ForUnit(unit);
Assert.Equal(typeof(TestUnit), unitKey.UnitType);
Assert.Equal((int)unit, unitKey.UnitValue);
Assert.Equal(typeof(TestUnit), unitKey.UnitEnumType);
Assert.Equal((int)unit, unitKey.UnitEnumValue);
}

[Theory]
Expand All @@ -53,8 +53,31 @@ public void ForUnit_ShouldCreateUnitKey(TestUnit unit)
public void Create_ShouldCreateUnitKey(int unitValue)
{
var unitKey = UnitKey.Create<TestUnit>(unitValue);
Assert.Equal(typeof(TestUnit), unitKey.UnitType);
Assert.Equal(unitValue, unitKey.UnitValue);
Assert.Equal(typeof(TestUnit), unitKey.UnitEnumType);
Assert.Equal(unitValue, unitKey.UnitEnumValue);
}

[Theory]
[InlineData(typeof(TestUnit), 1)]
[InlineData(typeof(TestUnit), 2)]
[InlineData(typeof(TestUnit), 3)]
public void Create_WithUnitTypeAndUnitValue_ShouldCreateUnitKey(Type unitType, int unitValue)
{
var unitKey = UnitKey.Create(unitType, unitValue);
Assert.Equal(unitType, unitKey.UnitEnumType);
Assert.Equal(unitValue, unitKey.UnitEnumValue);
}

[Fact]
public void Create_WithNullUnitType_ShouldThrowArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => UnitKey.Create(null!, 0));
}

[Fact]
public void Create_WithNonEnumType_ShouldThrowArgumentException()
{
Assert.Throws<ArgumentException>(() => UnitKey.Create(typeof(int), 1));
}

[Theory]
Expand All @@ -64,8 +87,8 @@ public void Create_ShouldCreateUnitKey(int unitValue)
public void ImplicitConversion_ShouldCreateUnitKey(TestUnit unit)
{
UnitKey unitKey = unit;
Assert.Equal(typeof(TestUnit), unitKey.UnitType);
Assert.Equal((int)unit, unitKey.UnitValue);
Assert.Equal(typeof(TestUnit), unitKey.UnitEnumType);
Assert.Equal((int)unit, unitKey.UnitEnumValue);
}

[Theory]
Expand Down Expand Up @@ -94,8 +117,8 @@ public void ToUnit_ShouldReturnEnum(TestUnit unit)
public void Default_InitializesWithoutAType()
{
var defaultUnitKey = default(UnitKey);
Assert.Null(defaultUnitKey.UnitType);
Assert.Equal(0, defaultUnitKey.UnitValue);
Assert.Null(defaultUnitKey.UnitEnumType);
Assert.Equal(0, defaultUnitKey.UnitEnumValue);
}

[Fact]
Expand Down Expand Up @@ -139,7 +162,7 @@ public void Deconstruct_ShouldReturnTheUnitTypeAndUnitValue()
[InlineData(TestUnit.Unit1, "TestUnit.Unit1")]
[InlineData(TestUnit.Unit2, "TestUnit.Unit2")]
[InlineData(TestUnit.Unit3, "TestUnit.Unit3")]
[InlineData((TestUnit)(-1), "UnitType: UnitsNet.Tests.UnitKeyTest+TestUnit, UnitValue = -1")]
[InlineData((TestUnit)(-1), "UnitEnumType: UnitsNet.Tests.UnitKeyTest+TestUnit, UnitEnumValue = -1")]
public void GetDebuggerDisplay_ShouldReturnCorrectString(TestUnit unit, string expectedDisplay)
{
var unitKey = UnitKey.ForUnit(unit);
Expand All @@ -154,6 +177,6 @@ public void GetDebuggerDisplayWithDefault_ShouldReturnCorrectString()
var defaultUnitKey = default(UnitKey);
var display = defaultUnitKey.GetType().GetMethod("GetDebuggerDisplay", BindingFlags.NonPublic | BindingFlags.Instance)!
.Invoke(defaultUnitKey, null);
Assert.Equal("UnitType: , UnitValue = 0", display);
Assert.Equal("UnitEnumType: , UnitEnumValue = 0", display);
}
}
Loading