diff --git a/Directory.Packages.props b/Directory.Packages.props
index e8a9ef4c66..54479369db 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,6 +3,7 @@
true
+
diff --git a/UnitsNet.Benchmark/Conversions/FromString/ParseUnitBenchmarks.cs b/UnitsNet.Benchmark/Conversions/FromString/ParseUnitBenchmarks.cs
index cf4ab78ec4..5cd182185c 100644
--- a/UnitsNet.Benchmark/Conversions/FromString/ParseUnitBenchmarks.cs
+++ b/UnitsNet.Benchmark/Conversions/FromString/ParseUnitBenchmarks.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using UnitsNet.Units;
@@ -10,6 +11,9 @@ namespace UnitsNet.Benchmark.Conversions.FromString;
[SimpleJob(RuntimeMoniker.Net80)]
public class ParseUnitBenchmarks
{
+ private const int NbAbbreviations = 1000;
+
+ private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
private readonly Random _random = new(42);
private string[] _densityUnits;
private string[] _massUnits;
@@ -17,37 +21,44 @@ public class ParseUnitBenchmarks
private string[] _volumeFlowUnits;
private string[] _volumeUnits = [];
- [Params(1000)]
- public int NbAbbreviations { get; set; }
-
[GlobalSetup(Target = nameof(ParseMassUnit))]
public void PrepareMassUnits()
{
_massUnits = _random.GetItems(["mg", "g", "kg", "lbs", "Mlbs"], NbAbbreviations);
+ // initializes the QuantityInfoLookup and the abbreviations cache
+ Mass.TryParseUnit("_invalid", Culture, out _);
}
[GlobalSetup(Target = nameof(ParseVolumeUnit))]
public void PrepareVolumeUnits()
{
_volumeUnits = _random.GetItems(["ml", "l", "L", "cm³", "m³"], NbAbbreviations);
+ // initializes the QuantityInfoLookup and the abbreviations cache
+ Volume.TryParseUnit("_invalid", Culture, out _);
}
[GlobalSetup(Target = nameof(ParseDensityUnit))]
public void PrepareDensityUnits()
{
_densityUnits = _random.GetRandomAbbreviations(UnitsNetSetup.Default.UnitAbbreviations, NbAbbreviations);
+ // initializes the QuantityInfoLookup and the abbreviations cache
+ Density.TryParseUnit("_invalid", Culture, out _);
}
[GlobalSetup(Target = nameof(ParsePressureUnit))]
public void PreparePressureUnits()
{
_pressureUnits = _random.GetRandomAbbreviations(UnitsNetSetup.Default.UnitAbbreviations, NbAbbreviations);
+ // initializes the QuantityInfoLookup and the abbreviations cache
+ Pressure.TryParseUnit("_invalid", Culture, out _);
}
[GlobalSetup(Target = nameof(ParseVolumeFlowUnit))]
public void PrepareVolumeFlowUnits()
{
_volumeFlowUnits = _random.GetRandomAbbreviations(UnitsNetSetup.Default.UnitAbbreviations, NbAbbreviations);
+ // initializes the QuantityInfoLookup and the abbreviations cache
+ VolumeFlow.TryParseUnit("_invalid", Culture, out _);
}
[Benchmark(Baseline = true)]
@@ -56,7 +67,7 @@ public MassUnit ParseMassUnit()
MassUnit unit = default;
foreach (var unitToParse in _massUnits)
{
- unit = Mass.ParseUnit(unitToParse);
+ unit = Mass.ParseUnit(unitToParse, Culture);
}
return unit;
@@ -68,7 +79,7 @@ public VolumeUnit ParseVolumeUnit()
VolumeUnit unit = default;
foreach (var unitToParse in _volumeUnits)
{
- unit = Volume.ParseUnit(unitToParse);
+ unit = Volume.ParseUnit(unitToParse, Culture);
}
return unit;
@@ -80,7 +91,7 @@ public DensityUnit ParseDensityUnit()
DensityUnit unit = default;
foreach (var unitToParse in _densityUnits)
{
- unit = Density.ParseUnit(unitToParse);
+ unit = Density.ParseUnit(unitToParse, Culture);
}
return unit;
@@ -92,7 +103,7 @@ public PressureUnit ParsePressureUnit()
PressureUnit unit = default;
foreach (var unitToParse in _pressureUnits)
{
- unit = Pressure.ParseUnit(unitToParse);
+ unit = Pressure.ParseUnit(unitToParse, Culture);
}
return unit;
@@ -104,7 +115,7 @@ public VolumeFlowUnit ParseVolumeFlowUnit()
VolumeFlowUnit unit = default;
foreach (var unitToParse in _volumeFlowUnits)
{
- unit = VolumeFlow.ParseUnit(unitToParse);
+ unit = VolumeFlow.ParseUnit(unitToParse, Culture);
}
return unit;
diff --git a/UnitsNet.Benchmark/Conversions/FromString/QuantityFromStringBenchmarks.cs b/UnitsNet.Benchmark/Conversions/FromString/QuantityFromStringBenchmarks.cs
new file mode 100644
index 0000000000..8abb153032
--- /dev/null
+++ b/UnitsNet.Benchmark/Conversions/FromString/QuantityFromStringBenchmarks.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using UnitsNet.Units;
+
+namespace UnitsNet.Benchmark.Conversions.FromString;
+
+[MemoryDiagnoser]
+[SimpleJob(RuntimeMoniker.Net48)]
+[SimpleJob(RuntimeMoniker.Net80)]
+public class QuantityFromStringBenchmarks
+{
+ private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
+ private static readonly string ValueToParse = 123.456.ToString(Culture);
+
+ private readonly Random _random = new(42);
+ private string[] _quantitiesToParse;
+
+ [Params(1000)]
+ public int NbAbbreviations { get; set; }
+
+ [GlobalSetup(Target = nameof(FromMassString))]
+ public void PrepareMassStrings()
+ {
+ // can't have "mg" or "g" (see Acceleration.StandardGravity) and who knows what more...
+ _quantitiesToParse = _random.GetItems(["kg", "lbs", "Mlbs"], NbAbbreviations).Select(abbreviation => $"{ValueToParse} {abbreviation}").ToArray();
+ }
+
+ [GlobalSetup(Target = nameof(FromVolumeUnitAbbreviation))]
+ public void PrepareVolumeStrings()
+ {
+ _quantitiesToParse = _random.GetItems(["ml", "l", "cm³", "m³"], NbAbbreviations).Select(abbreviation => $"{ValueToParse} {abbreviation}").ToArray();;
+ }
+
+ [GlobalSetup(Target = nameof(FromPressureUnitAbbreviation))]
+ public void PreparePressureUnits()
+ {
+ _quantitiesToParse = _random.GetRandomAbbreviations(UnitsNetSetup.Default.UnitAbbreviations, NbAbbreviations).Select(abbreviation => $"{ValueToParse} {abbreviation}").ToArray();;
+ }
+
+ [GlobalSetup(Target = nameof(FromVolumeFlowUnitAbbreviation))]
+ public void PrepareVolumeFlowUnits()
+ {
+ // can't have "bpm" (see Frequency)
+ _quantitiesToParse =
+ _random.GetItems(
+ UnitsNetSetup.Default.UnitAbbreviations.GetAllUnitAbbreviationsForQuantity(typeof(VolumeFlowUnit)).Where(x => x != "bpm").ToArray(),
+ NbAbbreviations).Select(abbreviation => $"{ValueToParse} {abbreviation}").ToArray();
+ }
+
+ [Benchmark(Baseline = true)]
+ public IQuantity FromMassString()
+ {
+ IQuantity quantity = null;
+ foreach (var quantityString in _quantitiesToParse)
+ {
+ quantity = Quantity.Parse(Culture, typeof(Mass), quantityString);
+ }
+
+ return quantity;
+ }
+
+ [Benchmark(Baseline = false)]
+ public IQuantity FromVolumeUnitAbbreviation()
+ {
+ IQuantity quantity = null;
+ foreach (var quantityString in _quantitiesToParse)
+ {
+ quantity = Quantity.Parse(Culture, typeof(Volume), quantityString);
+ }
+
+ return quantity;
+ }
+
+ [Benchmark(Baseline = false)]
+ public IQuantity FromPressureUnitAbbreviation()
+ {
+ IQuantity quantity = null;
+ foreach (var quantityString in _quantitiesToParse)
+ {
+ quantity = Quantity.Parse(Culture, typeof(Pressure), quantityString);
+ }
+
+ return quantity;
+ }
+
+ [Benchmark(Baseline = false)]
+ public IQuantity FromVolumeFlowUnitAbbreviation()
+ {
+ IQuantity quantity = null;
+ foreach (var quantityString in _quantitiesToParse)
+ {
+ quantity = Quantity.Parse(Culture, typeof(VolumeFlow), quantityString);
+ }
+
+ return quantity;
+ }
+}
diff --git a/UnitsNet.Benchmark/Conversions/FromString/QuantityFromUnitAbbreviationBenchmarks.cs b/UnitsNet.Benchmark/Conversions/FromString/QuantityFromUnitAbbreviationBenchmarks.cs
new file mode 100644
index 0000000000..9a6b3ae576
--- /dev/null
+++ b/UnitsNet.Benchmark/Conversions/FromString/QuantityFromUnitAbbreviationBenchmarks.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using UnitsNet.Units;
+
+namespace UnitsNet.Benchmark.Conversions.FromString;
+
+[MemoryDiagnoser]
+[SimpleJob(RuntimeMoniker.Net48)]
+[SimpleJob(RuntimeMoniker.Net80)]
+public class QuantityFromUnitAbbreviationBenchmarks
+{
+ private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
+ private readonly Random _random = new(42);
+ private string[] _massUnits;
+ private string[] _pressureUnits;
+ private string[] _volumeFlowUnits;
+ private string[] _volumeUnits = [];
+
+ [Params(1000)]
+ public int NbAbbreviations { get; set; }
+
+ [GlobalSetup(Target = nameof(FromMassUnitAbbreviation))]
+ public void PrepareMassUnits()
+ {
+ // can't have "mg" or "g" (see Acceleration.StandardGravity) and who knows what more...
+ _massUnits = _random.GetItems(["kg", "lbs", "Mlbs"], NbAbbreviations);
+ }
+
+ [GlobalSetup(Target = nameof(FromVolumeUnitAbbreviation))]
+ public void PrepareVolumeUnits()
+ {
+ _volumeUnits = _random.GetItems(["ml", "l", "cm³", "m³"], NbAbbreviations);
+ }
+
+ [GlobalSetup(Target = nameof(FromPressureUnitAbbreviation))]
+ public void PreparePressureUnits()
+ {
+ _pressureUnits = _random.GetRandomAbbreviations(UnitsNetSetup.Default.UnitAbbreviations, NbAbbreviations);
+ }
+
+ [GlobalSetup(Target = nameof(FromVolumeFlowUnitAbbreviation))]
+ public void PrepareVolumeFlowUnits()
+ {
+ // can't have "bpm" (see Frequency)
+ _volumeFlowUnits =
+ _random.GetItems(
+ UnitsNetSetup.Default.UnitAbbreviations.GetAllUnitAbbreviationsForQuantity(typeof(VolumeFlowUnit)).Where(x => x != "bpm").ToArray(),
+ NbAbbreviations);
+ }
+
+ [Benchmark(Baseline = true)]
+ public IQuantity FromMassUnitAbbreviation()
+ {
+ IQuantity quantity = null;
+ foreach (var unitToParse in _massUnits)
+ {
+ quantity = Quantity.FromUnitAbbreviation(Culture, 1, unitToParse);
+ }
+
+ return quantity;
+ }
+
+ [Benchmark(Baseline = false)]
+ public IQuantity FromVolumeUnitAbbreviation()
+ {
+ IQuantity quantity = null;
+ foreach (var unitToParse in _volumeUnits)
+ {
+ quantity = Quantity.FromUnitAbbreviation(Culture, 1, unitToParse);
+ }
+
+ return quantity;
+ }
+
+ [Benchmark(Baseline = false)]
+ public IQuantity FromPressureUnitAbbreviation()
+ {
+ IQuantity quantity = null;
+ foreach (var unitToParse in _pressureUnits)
+ {
+ quantity = Quantity.FromUnitAbbreviation(Culture, 1, unitToParse);
+ }
+
+ return quantity;
+ }
+
+ [Benchmark(Baseline = false)]
+ public IQuantity FromVolumeFlowUnitAbbreviation()
+ {
+ IQuantity quantity = null;
+ foreach (var unitToParse in _volumeFlowUnits)
+ {
+ quantity = Quantity.FromUnitAbbreviation(Culture, 1, unitToParse);
+ }
+
+ return quantity;
+ }
+}
diff --git a/UnitsNet.Benchmark/Conversions/FromString/QuantityFromUnitNameBenchmarks.cs b/UnitsNet.Benchmark/Conversions/FromString/QuantityFromUnitNameBenchmarks.cs
new file mode 100644
index 0000000000..f337295e9d
--- /dev/null
+++ b/UnitsNet.Benchmark/Conversions/FromString/QuantityFromUnitNameBenchmarks.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Linq;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+
+namespace UnitsNet.Benchmark.Conversions.FromString;
+
+[MemoryDiagnoser]
+[SimpleJob(RuntimeMoniker.Net48)]
+[SimpleJob(RuntimeMoniker.Net80)]
+public class QuantityFromUnitNameBenchmarks
+{
+ private readonly Random _random = new(42);
+ private string[] _unitNames;
+
+ [Params(1000)]
+ public int NbAbbreviations { get; set; }
+
+ [GlobalSetup(Target = nameof(FromMassUnitName))]
+ public void PrepareMassUnits()
+ {
+ _unitNames = _random.GetItems(Mass.Info.UnitInfos.Select(x => x.Name).ToArray(), NbAbbreviations);
+ }
+
+ [GlobalSetup(Target = nameof(FromVolumeUnitName))]
+ public void PrepareVolumeUnits()
+ {
+ _unitNames = _random.GetItems(Volume.Info.UnitInfos.Select(x => x.Name).ToArray(), NbAbbreviations);
+ }
+
+ [GlobalSetup(Target = nameof(FromPressureUnitName))]
+ public void PreparePressureUnits()
+ {
+ _unitNames = _random.GetItems(Pressure.Info.UnitInfos.Select(x => x.Name).ToArray(), NbAbbreviations);
+ }
+
+ [GlobalSetup(Target = nameof(FromVolumeFlowUnitName))]
+ public void PrepareVolumeFlowUnits()
+ {
+ _unitNames = _random.GetItems(VolumeFlow.Info.UnitInfos.Select(x => x.Name).ToArray(), NbAbbreviations);
+ }
+
+ [Benchmark(Baseline = true)]
+ public IQuantity FromMassUnitName()
+ {
+ IQuantity quantity = null;
+ foreach (var unitName in _unitNames)
+ {
+ quantity = Quantity.From(1, nameof(Mass), unitName);
+ }
+
+ return quantity;
+ }
+
+ [Benchmark(Baseline = false)]
+ public IQuantity FromVolumeUnitName()
+ {
+ IQuantity quantity = null;
+ foreach (var unitName in _unitNames)
+ {
+ quantity = Quantity.From(1, nameof(Volume), unitName);
+ }
+
+ return quantity;
+ }
+
+ [Benchmark(Baseline = false)]
+ public IQuantity FromPressureUnitName()
+ {
+ IQuantity quantity = null;
+ foreach (var unitName in _unitNames)
+ {
+ quantity = Quantity.From(1, nameof(Pressure), unitName);
+ }
+
+ return quantity;
+ }
+
+ [Benchmark(Baseline = false)]
+ public IQuantity FromVolumeFlowUnitName()
+ {
+ IQuantity quantity = null;
+ foreach (var unitName in _unitNames)
+ {
+ quantity = Quantity.From(1, nameof(VolumeFlow), unitName);
+ }
+
+ return quantity;
+ }
+}
diff --git a/UnitsNet.Benchmark/Conversions/FromString/TryParseInvalidUnitBenchmarks.cs b/UnitsNet.Benchmark/Conversions/FromString/TryParseInvalidUnitBenchmarks.cs
index 13515c63a7..bd24c1c579 100644
--- a/UnitsNet.Benchmark/Conversions/FromString/TryParseInvalidUnitBenchmarks.cs
+++ b/UnitsNet.Benchmark/Conversions/FromString/TryParseInvalidUnitBenchmarks.cs
@@ -2,11 +2,11 @@
// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet.
using System;
+using System.Globalization;
using System.Linq;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
-using UnitsNet.Units;
namespace UnitsNet.Benchmark.Conversions.FromString;
@@ -15,16 +15,22 @@ namespace UnitsNet.Benchmark.Conversions.FromString;
[SimpleJob(RuntimeMoniker.Net80)]
public class TryParseInvalidUnitBenchmarks
{
+ private const int NbAbbreviations = 1000;
+
+ private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
private readonly Random _random = new(42);
private string[] _invalidUnits = [];
- [Params(1000)]
- public int NbAbbreviations { get; set; }
-
[GlobalSetup]
public void Setup()
{
_invalidUnits = Enumerable.Range(0, NbAbbreviations).Select(_ => GenerateInvalidUnit()).ToArray();
+ // initializes the QuantityInfoLookup and the abbreviations cache
+ Mass.TryParseUnit("_invalid", Culture, out _);
+ Volume.TryParseUnit("_invalid", Culture, out _);
+ Density.TryParseUnit("_invalid", Culture, out _);
+ Pressure.TryParseUnit("_invalid", Culture, out _);
+ VolumeFlow.TryParseUnit("_invalid", Culture, out _);
}
private string GenerateInvalidUnit()
@@ -46,7 +52,7 @@ public bool TryParseMassUnit()
var success = true;
foreach (var unitToParse in _invalidUnits)
{
- success = Mass.TryParseUnit(unitToParse, out MassUnit _);
+ success = Mass.TryParseUnit(unitToParse, Culture, out _);
}
return success;
@@ -58,43 +64,43 @@ public bool TryParseVolumeUnit()
var success = true;
foreach (var unitToParse in _invalidUnits)
{
- success = Volume.TryParseUnit(unitToParse, out _);
+ success = Volume.TryParseUnit(unitToParse, Culture, out _);
}
return success;
}
[Benchmark(Baseline = false)]
- public bool ParseDensityUnit()
+ public bool TryParseDensityUnit()
{
var success = true;
foreach (var unitToParse in _invalidUnits)
{
- success = Density.TryParseUnit(unitToParse, out _);
+ success = Density.TryParseUnit(unitToParse, Culture, out _);
}
return success;
}
[Benchmark(Baseline = false)]
- public bool ParsePressureUnit()
+ public bool TryParsePressureUnit()
{
var success = true;
foreach (var unitToParse in _invalidUnits)
{
- success = Pressure.TryParseUnit(unitToParse, out _);
+ success = Pressure.TryParseUnit(unitToParse, Culture, out _);
}
return success;
}
[Benchmark(Baseline = false)]
- public bool ParseVolumeFlowUnit()
+ public bool TryParseVolumeFlowUnit()
{
var success = true;
foreach (var unitToParse in _invalidUnits)
{
- success = VolumeFlow.TryParseUnit(unitToParse, out _);
+ success = VolumeFlow.TryParseUnit(unitToParse, Culture, out _);
}
return success;
diff --git a/UnitsNet.Benchmark/Conversions/ToString/ToStringWithDefaultPrecisionBenchmarks.cs b/UnitsNet.Benchmark/Conversions/ToString/ToStringWithDefaultPrecisionBenchmarks.cs
new file mode 100644
index 0000000000..6d0560ebfb
--- /dev/null
+++ b/UnitsNet.Benchmark/Conversions/ToString/ToStringWithDefaultPrecisionBenchmarks.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using UnitsNet.Units;
+
+namespace UnitsNet.Benchmark.Conversions.ToString;
+
+[MemoryDiagnoser]
+[SimpleJob(RuntimeMoniker.Net48)]
+[SimpleJob(RuntimeMoniker.Net80)]
+public class ToStringWithDefaultPrecisionBenchmarks
+{
+ private static readonly double Value = 123.456;
+ private readonly Random _random = new(42);
+
+ private Mass[] _masses = [];
+ private VolumeFlow[] _volumeFlows = [];
+
+ [Params(1000)]
+ public int NbConversions { get; set; }
+
+ [Params("G", "S", "E", "N", "A")]
+ public string Format { get; set; }
+
+ [GlobalSetup(Target = nameof(MassToString))]
+ public void PrepareMassesToTest()
+ {
+ _masses = _random.GetRandomQuantities(Value, Mass.Units, NbConversions).ToArray();
+ }
+
+ [GlobalSetup(Target = nameof(VolumeFlowToString))]
+ public void PrepareVolumeFlowsToTest()
+ {
+ _volumeFlows = _random.GetRandomQuantities(Value, VolumeFlow.Units, NbConversions).ToArray();
+ }
+
+ [Benchmark(Baseline = true)]
+ public void MassToString()
+ {
+ foreach (Mass quantity in _masses)
+ {
+ var result = quantity.ToString(Format, CultureInfo.InvariantCulture);
+ }
+ }
+
+ [Benchmark]
+ public void VolumeFlowToString()
+ {
+ foreach (VolumeFlow quantity in _volumeFlows)
+ {
+ var result = quantity.ToString(Format, CultureInfo.InvariantCulture);
+ }
+ }
+}
diff --git a/UnitsNet.Benchmark/Conversions/ToValue/ConvertValueBenchmarks.cs b/UnitsNet.Benchmark/Conversions/ToValue/ConvertValueBenchmarks.cs
index 5c3ca7586c..ff02b0a189 100644
--- a/UnitsNet.Benchmark/Conversions/ToValue/ConvertValueBenchmarks.cs
+++ b/UnitsNet.Benchmark/Conversions/ToValue/ConvertValueBenchmarks.cs
@@ -51,7 +51,7 @@ public double ConvertFromQuantity()
[GlobalSetup]
public void PrepareTo_ConvertWith_FullyCachedFrozenDictionary()
{
- var nbQuantities = Quantity.Infos.Length;
+ var nbQuantities = Quantity.Infos.Count;
}
}
diff --git a/UnitsNet.Benchmark/Enums/BoxedEnumToIntegerBenchmarks.cs b/UnitsNet.Benchmark/Enums/BoxedEnumToIntegerBenchmarks.cs
new file mode 100644
index 0000000000..18cd6f3432
--- /dev/null
+++ b/UnitsNet.Benchmark/Enums/BoxedEnumToIntegerBenchmarks.cs
@@ -0,0 +1,59 @@
+// 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.Runtime.CompilerServices;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using UnitsNet.Units;
+
+namespace UnitsNet.Benchmark.Enums;
+
+[MemoryDiagnoser]
+[SimpleJob(RuntimeMoniker.Net48)]
+[SimpleJob(RuntimeMoniker.Net80)]
+public class BoxedEnumToIntegerBenchmarks
+{
+ private const int NbIterations = 1000;
+
+ private static readonly Enum Unit = MassUnit.Gram;
+
+ [Benchmark(Baseline = true)]
+ public int ConvertToInt32()
+ {
+ Enum unit = Unit;
+ var total = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ total += Convert.ToInt32(unit);
+ }
+
+ return total;
+ }
+
+ [Benchmark(Baseline = false)]
+ public int ConvertWithCast()
+ {
+ Enum unit = Unit;
+ var total = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ total += (int)(object)unit;
+ }
+
+ return total;
+ }
+
+ [Benchmark(Baseline = false)]
+ public int ConvertWithUnsafe()
+ {
+ Enum unit = Unit;
+ var total = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ total += Unsafe.Unbox(unit);
+ }
+
+ return total;
+ }
+}
diff --git a/UnitsNet.Benchmark/Enums/EnumToIntegerBenchmarks.cs b/UnitsNet.Benchmark/Enums/EnumToIntegerBenchmarks.cs
new file mode 100644
index 0000000000..75fc0b2fc3
--- /dev/null
+++ b/UnitsNet.Benchmark/Enums/EnumToIntegerBenchmarks.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Runtime.CompilerServices;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using UnitsNet.Units;
+
+namespace UnitsNet.Benchmark.Enums;
+
+[ShortRunJob(RuntimeMoniker.Net48)]
+[ShortRunJob(RuntimeMoniker.Net80)]
+public class EnumToIntegerBenchmarks
+{
+ private const int NbIterations = 1000;
+
+ private const MassUnit Unit = MassUnit.Gram;
+
+ [Benchmark(Baseline = true)]
+ public int ConvertToInt32()
+ {
+ var total = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ total += Convert.ToInt32(Unit);
+ }
+
+ return total;
+ }
+
+ [Benchmark(Baseline = false)]
+ public int ConvertWithCast()
+ {
+ var total = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ total += (int)Unit;
+ }
+
+ return total;
+ }
+
+ // #if NET
+ [Benchmark(Baseline = false)]
+ public int ConvertWithUnsafe()
+ {
+ MassUnit unit = Unit;
+ var total = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ total += Unsafe.As(ref unit);
+ }
+
+ return total;
+ }
+ // #endif
+}
diff --git a/UnitsNet.Benchmark/Enums/UnitKeyEqualsBenchmarks.cs b/UnitsNet.Benchmark/Enums/UnitKeyEqualsBenchmarks.cs
new file mode 100644
index 0000000000..e763b8233a
--- /dev/null
+++ b/UnitsNet.Benchmark/Enums/UnitKeyEqualsBenchmarks.cs
@@ -0,0 +1,60 @@
+// 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 BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using UnitsNet.Units;
+
+namespace UnitsNet.Benchmark.Enums;
+
+[SimpleJob(RuntimeMoniker.Net48)]
+[SimpleJob(RuntimeMoniker.Net80)]
+public class UnitKeyEqualsBenchmarks
+{
+ private const int NbIterations = 1000;
+
+ 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 UnitType = UnitKey.UnitType;
+ private readonly int UnitValue = UnitKey.UnitValue;
+
+ [Benchmark(Baseline = true)]
+ public bool EqualsRecord()
+ {
+ bool equal = false;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ equal = UnitKey.Equals(OtherUnitKey);
+ }
+
+ return equal;
+ }
+
+ [Benchmark(Baseline = false)]
+ public bool OperatorEqualsRecord()
+ {
+ bool equal = false;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ equal = UnitKey == OtherUnitKey;
+ }
+
+ return equal;
+ }
+
+ [Benchmark]
+ public bool OperatorEqualsManual()
+ {
+ bool equal = false;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ equal = UnitType == OtherUnitType && UnitValue == OtherUnitValue;
+ }
+
+ return equal;
+ }
+}
diff --git a/UnitsNet.Benchmark/Enums/UnitKeyHashCodeBenchmarks.cs b/UnitsNet.Benchmark/Enums/UnitKeyHashCodeBenchmarks.cs
new file mode 100644
index 0000000000..cdc09972c3
--- /dev/null
+++ b/UnitsNet.Benchmark/Enums/UnitKeyHashCodeBenchmarks.cs
@@ -0,0 +1,72 @@
+// 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 BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using UnitsNet.Units;
+
+namespace UnitsNet.Benchmark.Enums;
+
+// [MemoryDiagnoser]
+[SimpleJob(RuntimeMoniker.Net48)]
+[SimpleJob(RuntimeMoniker.Net80)]
+public class UnitKeyHashCodeBenchmarks
+{
+ private const int NbIterations = 1000;
+
+ private static readonly UnitKey UnitKey = UnitKey.ForUnit(VolumeUnit.CubicMeter);
+
+ private readonly Type UnitType = UnitKey.UnitType;
+ private readonly int UnitValue = UnitKey.UnitValue;
+
+ [Benchmark(Baseline = true)]
+ public int GetHashCodeRecord()
+ {
+ int hashCode = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ hashCode += UnitKey.GetHashCode();
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ public int GetCustomHashCode()
+ {
+ int hashCode = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+#if NET
+ hashCode += HashCode.Combine(UnitType, UnitValue);
+#else
+ hashCode += (UnitType.GetHashCode() * 397) ^ UnitValue;
+#endif
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ public int GetCustomHashCodeUnchecked()
+ {
+ int hashCode = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ if (UnitType == null)
+ {
+ hashCode += UnitValue;
+ }
+ else
+ {
+ unchecked
+ {
+ hashCode += (UnitType.GetHashCode() * 397) ^ UnitValue;
+ }
+ }
+ }
+
+ return hashCode;
+ }
+}
diff --git a/UnitsNet.Benchmark/Enums/UnitKeyToEnumBenchmarks.cs b/UnitsNet.Benchmark/Enums/UnitKeyToEnumBenchmarks.cs
new file mode 100644
index 0000000000..d70e9db227
--- /dev/null
+++ b/UnitsNet.Benchmark/Enums/UnitKeyToEnumBenchmarks.cs
@@ -0,0 +1,82 @@
+// 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 BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using UnitsNet.Units;
+
+namespace UnitsNet.Benchmark.Enums;
+
+[MemoryDiagnoser]
+[SimpleJob(RuntimeMoniker.Net48)]
+[SimpleJob(RuntimeMoniker.Net80)]
+public class UnitKeyToEnumBenchmarks
+{
+ private const int NbIterations = 500;
+ private static readonly UnitKey UnitKey = MassUnit.Gram;
+
+ [Benchmark(Baseline = true)]
+ public int ManualCast()
+ {
+ UnitKey unitKey = UnitKey;
+ var total = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ if ((MassUnit)unitKey.UnitValue == MassUnit.Gram)
+ {
+ total++;
+ }
+ }
+
+ return total;
+ }
+
+ [Benchmark(Baseline = false)]
+ public int ExplicitCast()
+ {
+ UnitKey unitKey = UnitKey;
+ var total = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ if ((MassUnit)unitKey == MassUnit.Gram)
+ {
+ total++;
+ }
+ }
+
+ return total;
+ }
+
+ [Benchmark(Baseline = false)]
+ public int ExplicitCastBoxed()
+ {
+ UnitKey unitKey = UnitKey;
+ var total = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ if (MassUnit.Gram.Equals((Enum)unitKey))
+ {
+ total++;
+ }
+ }
+
+ return total;
+ }
+
+ [Benchmark(Baseline = false)]
+ public int ToUnit()
+ {
+ UnitKey unitKey = UnitKey;
+ var total = 0;
+ for (var i = 0; i < NbIterations; i++)
+ {
+ if (unitKey.ToUnit() == MassUnit.Gram)
+ {
+ total++;
+ }
+ }
+
+ return total;
+ }
+}
diff --git a/UnitsNet.Benchmark/Initializations/UnitAbbreviationsCacheInitializationBenchmarks.cs b/UnitsNet.Benchmark/Initializations/UnitAbbreviationsCacheInitializationBenchmarks.cs
new file mode 100644
index 0000000000..26e6d9722b
--- /dev/null
+++ b/UnitsNet.Benchmark/Initializations/UnitAbbreviationsCacheInitializationBenchmarks.cs
@@ -0,0 +1,71 @@
+// 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 BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using UnitsNet.Units;
+
+namespace UnitsNet.Benchmark.Initializations;
+
+[MemoryDiagnoser]
+[SimpleJob(RuntimeMoniker.Net48)]
+[SimpleJob(RuntimeMoniker.Net80)]
+public class UnitAbbreviationsCacheInitializationBenchmarks
+{
+ [GlobalSetup]
+ public void InitializeUnitsNetSetup()
+ {
+ var quantities = Quantity.Infos.Count;
+ }
+
+ [Benchmark(Baseline = true)]
+ public string Default()
+ {
+ var cache = UnitAbbreviationsCache.CreateDefault();
+ return cache.GetDefaultAbbreviation(MassUnit.Gram);
+ }
+
+ [Benchmark]
+ public string EmptyWithCustomMapping()
+ {
+ var cache = new UnitAbbreviationsCache();
+ cache.MapUnitToDefaultAbbreviation(MassUnit.Gram, "zz");
+ return cache.GetDefaultAbbreviation(MassUnit.Gram);
+ }
+
+ [Benchmark]
+ public string WithSpecificQuantity()
+ {
+ var cache = new UnitAbbreviationsCache([Mass.Info]);
+ return cache.GetDefaultAbbreviation(MassUnit.Gram);
+ }
+
+ [Benchmark]
+ public string WithSpecificQuantityAndCustomMapping()
+ {
+ var cache = new UnitAbbreviationsCache([Mass.Info]);
+ cache.MapUnitToDefaultAbbreviation(MassUnit.Gram, "zz");
+ return cache.GetDefaultAbbreviation(MassUnit.Gram);
+ }
+
+ [Benchmark]
+ public string DefaultWithoutLookup()
+ {
+ var cache = UnitAbbreviationsCache.CreateDefault();
+ return cache.GetAbbreviations(Mass.Info.BaseUnitInfo)[0];
+ }
+
+ [Benchmark]
+ public string EmptyWithoutLookup()
+ {
+ var cache = new UnitAbbreviationsCache();
+ return cache.GetAbbreviations(Mass.Info.BaseUnitInfo)[0];
+ }
+
+ [Benchmark]
+ public string WithSpecificQuantityWithoutLookup()
+ {
+ var cache = new UnitAbbreviationsCache([Mass.Info]);
+ return cache.GetAbbreviations(Mass.Info.BaseUnitInfo)[0];
+ }
+}
diff --git a/UnitsNet.Benchmark/UnitsNet.Benchmark.csproj b/UnitsNet.Benchmark/UnitsNet.Benchmark.csproj
index cd95d63f0d..11e298fdd7 100644
--- a/UnitsNet.Benchmark/UnitsNet.Benchmark.csproj
+++ b/UnitsNet.Benchmark/UnitsNet.Benchmark.csproj
@@ -1,7 +1,8 @@
Exe
- net8.0;net9.0
+ net9.0;net48
+ preview4.0.0.04.0.0.0UnitsNet.Benchmark
diff --git a/UnitsNet.Serialization.JsonNet/AbbreviatedUnitsConverter.cs b/UnitsNet.Serialization.JsonNet/AbbreviatedUnitsConverter.cs
index 97cebf9eb9..11977d3f67 100644
--- a/UnitsNet.Serialization.JsonNet/AbbreviatedUnitsConverter.cs
+++ b/UnitsNet.Serialization.JsonNet/AbbreviatedUnitsConverter.cs
@@ -232,7 +232,7 @@ protected virtual Enum FindUnit(string unitAbbreviation, out QuantityInfo quanti
/// The default abbreviation as provided by the associated
protected string GetUnitAbbreviation(Enum unit)
{
- return _abbreviations.GetDefaultAbbreviation(unit.GetType(), Convert.ToInt32(unit), CultureInfo.InvariantCulture);
+ return _abbreviations.GetDefaultAbbreviation(unit, CultureInfo.InvariantCulture);
}
///
diff --git a/UnitsNet.Tests/QuantityTest.cs b/UnitsNet.Tests/QuantityTest.cs
index 3e9ca33dbe..0f47fd13c4 100644
--- a/UnitsNet.Tests/QuantityTest.cs
+++ b/UnitsNet.Tests/QuantityTest.cs
@@ -37,6 +37,13 @@ public void TryFrom_GivenNaNOrInfinity_ReturnsTrueAndQuantity(double value)
Assert.NotNull(parsedLength);
}
+ [Fact]
+ public void TryFrom_GivenNullUnit_ReturnsFalse()
+ {
+ Enum? nullUnit = null;
+ Assert.False(Quantity.TryFrom(1, nullUnit, out IQuantity? _));
+ }
+
[Fact]
public void From_GivenValueAndUnit_ReturnsQuantity()
{
@@ -68,7 +75,7 @@ public void Infos_ReturnsKnownQuantityInfoObjects()
var infos = Quantity.Infos;
Assert.Superset(knownQuantityInfos.ToHashSet(), infos.ToHashSet());
- Assert.Equal(QuantityCount, infos.Length);
+ Assert.Equal(QuantityCount, infos.Count);
}
[Fact]
@@ -91,9 +98,9 @@ public void TryGetUnitInfo_ReturnsUnitInfoForUnitEnumValue()
}
[Fact]
- public void GetUnitInfo_ThrowsKeyNotFoundExceptionIfNotFound()
+ public void GetUnitInfo_ThrowsUnitNotFoundExceptionIfNotFound()
{
- Assert.Throws(() => Quantity.GetUnitInfo(ConsoleColor.Red));
+ Assert.Throws(() => Quantity.GetUnitInfo(ConsoleColor.Red));
}
[Fact]
@@ -111,6 +118,41 @@ public void Parse_GivenValueAndUnit_ReturnsQuantity()
Assert.Equal(Pressure.FromMegabars(3), Quantity.Parse(InvariantCulture, typeof(Pressure), "3.0 Mbar"));
}
+ [Fact]
+ public void Parse_GivenInvalidType_ThrowsArgumentException()
+ {
+ Assert.Throws(() => Quantity.Parse(typeof(bool), "3 cm"));
+ }
+
+ [Theory]
+ [InlineData(123.45, "G", LengthUnit.Centimeter)]
+ [InlineData(1.234e-8, "E", PressureUnit.Millibar)]
+ public void Parse_WithDefaultCulture_ReturnsQuantity(double value, string format, Enum unit)
+ {
+ IQuantity expectedQuantity = Quantity.From(value, unit);
+ var valueAsString = expectedQuantity.ToString(format, null);
+ Type targetType = expectedQuantity.QuantityInfo.QuantityType;
+
+ IQuantity parsedQuantity = Quantity.Parse(targetType, valueAsString);
+
+ Assert.Equal(expectedQuantity, parsedQuantity);
+ }
+
+ [Theory]
+ [InlineData(123.45, "G", LengthUnit.Centimeter)]
+ [InlineData(1.234e-8, "E", PressureUnit.Millibar)]
+ public void TryParse_WithDefaultCulture_ReturnsQuantity(double value, string format, Enum unit)
+ {
+ IQuantity expectedQuantity = Quantity.From(value, unit);
+ var valueAsString = expectedQuantity.ToString(format, null);
+ Type targetType = expectedQuantity.QuantityInfo.QuantityType;
+
+ var success = Quantity.TryParse(targetType, valueAsString, out IQuantity? parsedQuantity);
+
+ Assert.True(success);
+ Assert.Equal(expectedQuantity, parsedQuantity);
+ }
+
[Fact]
public void QuantityNames_ReturnsKnownNames()
{
@@ -119,7 +161,7 @@ public void QuantityNames_ReturnsKnownNames()
var names = Quantity.Names;
Assert.Superset(knownNames.ToHashSet(), names.ToHashSet());
- Assert.Equal(QuantityCount, names.Length);
+ Assert.Equal(QuantityCount, names.Count);
}
[Fact]
@@ -177,5 +219,27 @@ public void Types_ReturnsKnownQuantityTypes()
Assert.Superset(knownQuantities.ToHashSet(), types.ToHashSet());
}
+
+ [Theory]
+ [InlineData(1, 0, 0, 0, 0, 0, 0)]
+ [InlineData(0, 1, 0, 0, 0, 0, 0)]
+ [InlineData(0, 0, 1, 0, 0, 0, 0)]
+ [InlineData(0, 0, 0, 1, 0, 0, 0)]
+ [InlineData(0, 0, 0, 0, 1, 0, 0)]
+ [InlineData(0, 0, 0, 0, 0, 1, 0)]
+ [InlineData(0, 0, 0, 0, 0, 0, 1)]
+ [InlineData(0, 0, 0, 0, 0, 0, 0)]
+ public void GetQuantitiesWithBaseDimensions_ReturnsTheExpectedQuantityInfos(int length, int mass, int time, int current, int temperature, int amount, int luminousIntensity)
+ {
+ var baseDimensions = new BaseDimensions(length, mass, time, current, temperature, amount, luminousIntensity);
+ Assert.All(Quantity.GetQuantitiesWithBaseDimensions(baseDimensions), info => Assert.True(info.BaseDimensions == baseDimensions));
+ Assert.NotEmpty(Quantity.GetQuantitiesWithBaseDimensions(baseDimensions));
+ }
+
+ [Fact]
+ public void GetQuantitiesWithBaseDimensions_WithNull_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => Quantity.GetQuantitiesWithBaseDimensions(null!));
+ }
}
}
diff --git a/UnitsNet.Tests/QuantityTests.cs b/UnitsNet.Tests/QuantityTests.cs
index 7af958ab80..22154965fb 100644
--- a/UnitsNet.Tests/QuantityTests.cs
+++ b/UnitsNet.Tests/QuantityTests.cs
@@ -139,10 +139,15 @@ void AssertFrom(string quantityName, string unitName, Enum expectedUnit)
}
[Fact]
- public void From_InvalidQuantityNameOrUnitName_ThrowsUnitNotFoundException()
+ public void From_InvalidQuantityName_ThrowsQuantityNotFoundException()
+ {
+ Assert.Throws(() => Quantity.From(5, "InvalidQuantity", "Kilogram"));
+ }
+
+ [Fact]
+ public void From_InvalidUnitName_ThrowsUnitNotFoundException()
{
Assert.Throws(() => Quantity.From(5, "Length", "InvalidUnit"));
- Assert.Throws(() => Quantity.From(5, "InvalidQuantity", "Kilogram"));
}
[Fact]
diff --git a/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs b/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs
index 26cb2a085e..e80090518a 100644
--- a/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs
+++ b/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs
@@ -3,7 +3,9 @@
using System;
using System.Globalization;
+using System.Linq;
using UnitsNet.Tests.CustomQuantities;
+using UnitsNet.Tests.Helpers;
using UnitsNet.Units;
using Xunit;
@@ -295,10 +297,53 @@ public void ToString_WithRussianCulture()
}
[Fact]
- public void GetDefaultAbbreviationThrowsNotImplementedExceptionIfNoneExist()
+ public void UnitAbbreviationsCacheDefaultReturnsUnitsNetSetupDefaultUnitAbbreviations()
{
- var unitAbbreviationCache = new UnitAbbreviationsCache();
- Assert.Throws(() => unitAbbreviationCache.GetDefaultAbbreviation(HowMuchUnit.AShitTon));
+ Assert.Equal(UnitsNetSetup.Default.UnitAbbreviations, UnitAbbreviationsCache.Default);
+ }
+
+ [Fact]
+ public void GetUnitAbbreviationsThrowsUnitNotFoundExceptionIfNoneExist()
+ {
+ Assert.Multiple(checks: [
+ () => Assert.Throws(() => new UnitAbbreviationsCache().GetUnitAbbreviations(MassUnit.Gram)),
+ () => Assert.Throws(() => new UnitAbbreviationsCache().GetUnitAbbreviations(typeof(MassUnit), (int)MassUnit.Gram))
+ ]);
+ }
+
+ [Fact]
+ public void GetDefaultAbbreviationThrowsUnitNotFoundExceptionIfNoneExist()
+ {
+ Assert.Multiple(checks: [
+ () => Assert.Throws(() => new UnitAbbreviationsCache().GetDefaultAbbreviation(MassUnit.Gram)),
+ () => Assert.Throws(() => new UnitAbbreviationsCache().GetDefaultAbbreviation(typeof(MassUnit), (int)MassUnit.Gram))
+ ]);
+ }
+
+ [Fact]
+ public void GetUnitAbbreviationsReturnsTheExpectedAbbreviationWhenConstructedWithTheSpecificQuantityInfo()
+ {
+ Assert.Multiple(checks:
+ [
+ () => { Assert.Equal("g", new UnitAbbreviationsCache([Mass.Info]).GetUnitAbbreviations(MassUnit.Gram, AmericanCulture)[0]); },
+ () => { Assert.Equal("g", new UnitAbbreviationsCache([Mass.Info]).GetUnitAbbreviations(typeof(MassUnit), (int)MassUnit.Gram, AmericanCulture)[0]); }
+ ]);
+ }
+
+ [Fact]
+ public void GetDefaultAbbreviationReturnsTheExpectedAbbreviationWhenConstructedWithTheSpecificQuantityInfo()
+ {
+ Assert.Multiple(checks:
+ [
+ () => { Assert.Equal("g", new UnitAbbreviationsCache([Mass.Info]).GetDefaultAbbreviation(MassUnit.Gram, AmericanCulture)); },
+ () => { Assert.Equal("g", new UnitAbbreviationsCache([Mass.Info]).GetDefaultAbbreviation(typeof(MassUnit), (int)MassUnit.Gram, AmericanCulture)); }
+ ]);
+ }
+
+ [Fact]
+ public void GetAbbreviationsThrowsArgumentNullExceptionWhenGivenANullUnitInfo()
+ {
+ Assert.Throws(() => new UnitAbbreviationsCache().GetAbbreviations(null!));
}
[Fact]
@@ -355,6 +400,30 @@ public void MapUnitToDefaultAbbreviation_GivenUnitAndCulture_SetsDefaultAbbrevia
Assert.Equal("m^2", cache.GetDefaultAbbreviation(AreaUnit.SquareMeter, AmericanCulture));
}
+ [Fact]
+ public void MapUnitToDefaultAbbreviation_GivenUnitAndNoCulture_SetsDefaultAbbreviationForUnitForCurrentCulture()
+ {
+ using var cultureScope = new CultureScope(NorwegianCultureName);
+ var cache = new UnitAbbreviationsCache([Mass.Info]);
+
+ cache.MapUnitToDefaultAbbreviation(MassUnit.Gram, "zz");
+
+ Assert.Equal("zz", cache.GetDefaultAbbreviation(MassUnit.Gram));
+ Assert.Equal("g", cache.GetDefaultAbbreviation(MassUnit.Gram, AmericanCulture));
+ }
+
+ [Fact]
+ public void MapUnitToDefaultAbbreviation_GivenUnitTypeAndValue_SetsDefaultAbbreviationForUnitForCurrentCulture()
+ {
+ using var cultureScope = new CultureScope(NorwegianCultureName);
+ var cache = new UnitAbbreviationsCache([Mass.Info]);
+
+ cache.MapUnitToDefaultAbbreviation(typeof(MassUnit), (int)MassUnit.Gram, null, "zz");
+
+ Assert.Equal("zz", cache.GetDefaultAbbreviation(MassUnit.Gram));
+ Assert.Equal("g", cache.GetDefaultAbbreviation(MassUnit.Gram, AmericanCulture));
+ }
+
[Fact]
public void MapUnitToDefaultAbbreviation_GivenCustomAbbreviation_SetsAbbreviationUsedByQuantityToString()
{
@@ -365,6 +434,19 @@ public void MapUnitToDefaultAbbreviation_GivenCustomAbbreviation_SetsAbbreviatio
Assert.Equal("1 m^2", Area.FromSquareMeters(1).ToString(newZealandCulture));
}
+ [Fact]
+ public void MapUnitToAbbreviation_GivenUnitTypeAndValue_AddsTheAbbreviationForUnitForCurrentCulture()
+ {
+ using var cultureScope = new CultureScope(NorwegianCultureName);
+ var cache = new UnitAbbreviationsCache([Mass.Info]);
+
+ cache.MapUnitToAbbreviation(typeof(MassUnit), (int)MassUnit.Gram, null, "zz");
+
+ Assert.Equal("zz", cache.GetUnitAbbreviations(MassUnit.Gram).Last());
+ Assert.Equal("g", cache.GetDefaultAbbreviation(MassUnit.Gram, AmericanCulture));
+ Assert.DoesNotContain("zz", cache.GetUnitAbbreviations(MassUnit.Gram, AmericanCulture));
+ }
+
[Fact]
public void MapUnitToAbbreviation_DoesNotInsertDuplicates()
{
diff --git a/UnitsNet.Tests/UnitConverterTest.cs b/UnitsNet.Tests/UnitConverterTest.cs
index fe7ae8c10c..17ffdb2305 100644
--- a/UnitsNet.Tests/UnitConverterTest.cs
+++ b/UnitsNet.Tests/UnitConverterTest.cs
@@ -148,9 +148,9 @@ public void ConvertByName_UnitTypeCaseInsensitive()
[Theory]
[InlineData(1, "UnknownQuantity", "Meter", "Centimeter")]
- public void ConvertByName_ThrowsUnitNotFoundExceptionOnUnknownQuantity(double inputValue, string quantityTypeName, string fromUnit, string toUnit)
+ public void ConvertByName_ThrowsQuantityNotFoundExceptionOnUnknownQuantity(double inputValue, string quantityTypeName, string fromUnit, string toUnit)
{
- Assert.Throws(() => UnitConverter.ConvertByName(inputValue, quantityTypeName, fromUnit, toUnit));
+ Assert.Throws(() => UnitConverter.ConvertByName(inputValue, quantityTypeName, fromUnit, toUnit));
}
[Theory]
@@ -195,9 +195,9 @@ public void ConvertByAbbreviation_ConvertsTheValueToGivenUnit(double expectedVal
[Theory]
[InlineData(1, "UnknownQuantity", "m", "cm")]
- public void ConvertByAbbreviation_ThrowsUnitNotFoundExceptionOnUnknownQuantity( double inputValue, string quantityTypeName, string fromUnit, string toUnit)
+ public void ConvertByAbbreviation_ThrowsQuantityNotFoundExceptionOnUnknownQuantity( double inputValue, string quantityTypeName, string fromUnit, string toUnit)
{
- Assert.Throws(() => UnitConverter.ConvertByAbbreviation(inputValue, quantityTypeName, fromUnit, toUnit));
+ Assert.Throws(() => UnitConverter.ConvertByAbbreviation(inputValue, quantityTypeName, fromUnit, toUnit));
}
[Theory]
diff --git a/UnitsNet.Tests/UnitKeyTest.cs b/UnitsNet.Tests/UnitKeyTest.cs
new file mode 100644
index 0000000000..28b78eeb47
--- /dev/null
+++ b/UnitsNet.Tests/UnitKeyTest.cs
@@ -0,0 +1,159 @@
+// 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.Reflection;
+using Xunit;
+
+namespace UnitsNet.Tests;
+
+public class UnitKeyTest
+{
+ public enum TestUnit
+ {
+ Unit1 = 1,
+ Unit2 = 2,
+ Unit3 = 3
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(3)]
+ public void Constructor_ShouldCreateUnitKey(int unitValue)
+ {
+ var unitKey = new UnitKey(typeof(TestUnit), unitValue);
+ Assert.Equal(typeof(TestUnit), unitKey.UnitType);
+ Assert.Equal(unitValue, unitKey.UnitValue);
+ }
+
+ [Fact]
+ public void Constructor_WithNullType_ShouldNotThrow()
+ {
+ var unitKey = new UnitKey(null!, 0);
+ Assert.Null(unitKey.UnitType);
+ Assert.Equal(0, unitKey.UnitValue);
+ }
+
+ [Theory]
+ [InlineData(TestUnit.Unit1)]
+ [InlineData(TestUnit.Unit2)]
+ [InlineData(TestUnit.Unit3)]
+ public void ForUnit_ShouldCreateUnitKey(TestUnit unit)
+ {
+ var unitKey = UnitKey.ForUnit(unit);
+ Assert.Equal(typeof(TestUnit), unitKey.UnitType);
+ Assert.Equal((int)unit, unitKey.UnitValue);
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(3)]
+ public void Create_ShouldCreateUnitKey(int unitValue)
+ {
+ var unitKey = UnitKey.Create(unitValue);
+ Assert.Equal(typeof(TestUnit), unitKey.UnitType);
+ Assert.Equal(unitValue, unitKey.UnitValue);
+ }
+
+ [Theory]
+ [InlineData(TestUnit.Unit1)]
+ [InlineData(TestUnit.Unit2)]
+ [InlineData(TestUnit.Unit3)]
+ public void ImplicitConversion_ShouldCreateUnitKey(TestUnit unit)
+ {
+ UnitKey unitKey = unit;
+ Assert.Equal(typeof(TestUnit), unitKey.UnitType);
+ Assert.Equal((int)unit, unitKey.UnitValue);
+ }
+
+ [Theory]
+ [InlineData(TestUnit.Unit1)]
+ [InlineData(TestUnit.Unit2)]
+ [InlineData(TestUnit.Unit3)]
+ public void ExplicitConversion_ShouldReturnEnum(TestUnit unit)
+ {
+ var unitKey = UnitKey.ForUnit(unit);
+ var result = (TestUnit)(Enum)unitKey;
+ Assert.Equal(unit, result);
+ }
+
+ [Theory]
+ [InlineData(TestUnit.Unit1)]
+ [InlineData(TestUnit.Unit2)]
+ [InlineData(TestUnit.Unit3)]
+ public void ToUnit_ShouldReturnEnum(TestUnit unit)
+ {
+ var unitKey = UnitKey.ForUnit(unit);
+ TestUnit result = unitKey.ToUnit();
+ Assert.Equal(unit, result);
+ }
+
+ [Fact]
+ public void Default_InitializesWithoutAType()
+ {
+ var defaultUnitKey = default(UnitKey);
+ Assert.Null(defaultUnitKey.UnitType);
+ Assert.Equal(0, defaultUnitKey.UnitValue);
+ }
+
+ [Fact]
+ public void Default_Equals_UnitKeyForUnit_ReturnsFalse()
+ {
+ var defaultUnitKey = default(UnitKey);
+ var unitKey = UnitKey.ForUnit(TestUnit.Unit1);
+ Assert.NotEqual(unitKey, defaultUnitKey);
+ }
+
+ [Fact]
+ public void Default_GetHashCode_ReturnsZero()
+ {
+ var defaultUnitKey = default(UnitKey);
+ Assert.Equal(0, defaultUnitKey.GetHashCode());
+ }
+
+ [Fact]
+ public void ToUnit_ShouldThrowInvalidOperationExceptionForMismatchedType()
+ {
+ var unitKey = UnitKey.ForUnit(TestUnit.Unit1);
+ Assert.Throws(() => unitKey.ToUnit());
+ }
+
+ [Fact]
+ public void DefaultToUnit_ShouldThrowInvalidOperationExceptionForMismatchedType()
+ {
+ var defaultUnitKey = default(UnitKey);
+ Assert.Throws(() => defaultUnitKey.ToUnit());
+ }
+
+ [Fact]
+ public void Deconstruct_ShouldReturnTheUnitTypeAndUnitValue()
+ {
+ (Type unitType, var unitValue) = UnitKey.ForUnit(TestUnit.Unit1);
+ Assert.Equal(typeof(TestUnit), unitType);
+ Assert.Equal(1, unitValue);
+ }
+
+ [Theory]
+ [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")]
+ public void GetDebuggerDisplay_ShouldReturnCorrectString(TestUnit unit, string expectedDisplay)
+ {
+ var unitKey = UnitKey.ForUnit(unit);
+ var display = unitKey.GetType().GetMethod("GetDebuggerDisplay", BindingFlags.NonPublic | BindingFlags.Instance)!
+ .Invoke(unitKey, null);
+ Assert.Equal(expectedDisplay, display);
+ }
+
+ [Fact]
+ 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);
+ }
+}
diff --git a/UnitsNet/CustomCode/Quantity.cs b/UnitsNet/CustomCode/Quantity.cs
index 29187f0f90..d606ea55da 100644
--- a/UnitsNet/CustomCode/Quantity.cs
+++ b/UnitsNet/CustomCode/Quantity.cs
@@ -2,42 +2,42 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
-using System.Linq;
using UnitsNet.Units;
namespace UnitsNet
{
public partial class Quantity
{
- private static QuantityInfoLookup Default => UnitsNetSetup.Default.QuantityInfoLookup;
+ private static QuantityInfoLookup Quantities => UnitsNetSetup.Default.QuantityInfoLookup;
+ private static UnitParser UnitParser => UnitsNetSetup.Default.UnitParser;
///
- /// All enum value names of , such as "Length" and "Mass".
+ /// All quantity names of , such as "Length" and "Mass".
///
- public static string[] Names { get => Default.Names; }
+ public static IReadOnlyCollection Names => Quantities.Names;
///
/// All quantity information objects, such as and .
///
- public static QuantityInfo[] Infos => Default.Infos;
+ public static IReadOnlyList Infos => Quantities.Infos;
///
/// Get for a given unit enum value.
///
- public static UnitInfo GetUnitInfo(Enum unitEnum) => Default.GetUnitInfo(unitEnum);
+ public static UnitInfo GetUnitInfo(Enum unitEnum) => Quantities.GetUnitInfo(unitEnum);
///
/// Try to get for a given unit enum value.
///
public static bool TryGetUnitInfo(Enum unitEnum, [NotNullWhen(true)] out UnitInfo? unitInfo) =>
- Default.TryGetUnitInfo(unitEnum, out unitInfo);
+ Quantities.TryGetUnitInfo(unitEnum, out unitInfo);
///
///
///
///
///
- public static void AddUnitInfo(Enum unit, UnitInfo unitInfo) => Default.AddUnitInfo(unit, unitInfo);
+ public static void AddUnitInfo(Enum unit, UnitInfo unitInfo) => Quantities.AddUnitInfo(unitInfo);
///
/// Dynamically constructs a quantity from a numeric value and a unit enum value.
@@ -60,14 +60,15 @@ public static IQuantity From(double value, Enum unit)
/// The invariant quantity name, such as "Length". Does not support localization.
/// The invariant unit enum name, such as "Meter". Does not support localization.
/// An object.
- /// Unit value is not a known unit enum type.
+ ///
+ /// Thrown when no quantity information is found for the specified quantity name.
+ ///
+ ///
+ /// Thrown when no unit is found for the specified quantity name and unit name.
+ ///
public static IQuantity From(double value, string quantityName, string unitName)
{
- // Get enum value for this unit, f.ex. LengthUnit.Meter for unit name "Meter".
- return UnitConverter.TryParseUnit(quantityName, unitName, out Enum? unitValue) &&
- TryFrom(value, unitValue, out IQuantity? quantity)
- ? quantity
- : throw new UnitNotFoundException($"Unit [{unitName}] not found for quantity [{quantityName}].");
+ return From(value, Quantities.GetUnitByName(quantityName, unitName).Value);
}
///
@@ -105,20 +106,7 @@ public static IQuantity From(double value, string quantityName, string unitName)
/// Multiple units found matching the given unit abbreviation.
public static IQuantity FromUnitAbbreviation(IFormatProvider? formatProvider, double value, string unitAbbreviation)
{
- // TODO Optimize this with UnitValueAbbreviationLookup via UnitAbbreviationsCache.TryGetUnitValueAbbreviationLookup.
- List units = GetUnitsForAbbreviation(formatProvider, unitAbbreviation);
- if (units.Count > 1)
- {
- throw new AmbiguousUnitParseException($"Multiple units found matching the given unit abbreviation: {unitAbbreviation}");
- }
-
- if (units.Count == 0)
- {
- throw new UnitNotFoundException($"Unit abbreviation {unitAbbreviation} is not known. Did you pass in a custom unit abbreviation defined outside the UnitsNet library? This is currently not supported.");
- }
-
- Enum unit = units.Single();
- return From(value, unit);
+ return From(value, UnitParser.GetUnitFromAbbreviation(unitAbbreviation, formatProvider).Value);
}
///
@@ -131,10 +119,13 @@ public static IQuantity FromUnitAbbreviation(IFormatProvider? formatProvider, do
/// True if successful with assigned the value, otherwise false.
public static bool TryFrom(double value, string quantityName, string unitName, [NotNullWhen(true)] out IQuantity? quantity)
{
- quantity = default;
-
- return UnitConverter.TryParseUnit(quantityName, unitName, out Enum? unitValue) &&
- TryFrom(value, unitValue, out quantity);
+ if (Quantities.TryGetUnitByName(quantityName, unitName, out UnitInfo? unitInfo))
+ {
+ return TryFrom(value, unitInfo.Value, out quantity);
+ }
+
+ quantity = null;
+ return false;
}
///
@@ -173,20 +164,17 @@ public static bool TryFromUnitAbbreviation(double value, string unitAbbreviation
/// Unit value is not a known unit enum type.
public static bool TryFromUnitAbbreviation(IFormatProvider? formatProvider, double value, string unitAbbreviation, [NotNullWhen(true)] out IQuantity? quantity)
{
- // TODO Optimize this with UnitValueAbbreviationLookup via UnitAbbreviationsCache.TryGetUnitValueAbbreviationLookup.
- List units = GetUnitsForAbbreviation(formatProvider, unitAbbreviation);
- if (units.Count == 1)
+ if (UnitParser.TryGetUnitFromAbbreviation(unitAbbreviation, formatProvider, out UnitInfo? unitInfo))
{
- Enum? unit = units.SingleOrDefault();
- return TryFrom(value, unit, out quantity);
+ return TryFrom(value, unitInfo.Value, out quantity);
}
- quantity = default;
+ quantity = null;
return false;
}
///
- public static IQuantity Parse(Type quantityType, string quantityString) => Default.Parse(null, quantityType, quantityString);
+ public static IQuantity Parse(Type quantityType, string quantityString) => Parse(null, quantityType, quantityString);
///
/// Dynamically parse a quantity string representation.
@@ -199,12 +187,22 @@ public static bool TryFromUnitAbbreviation(IFormatProvider? formatProvider, doub
/// Type must be of type UnitsNet.IQuantity -or- Type is not a known quantity type.
public static IQuantity Parse(IFormatProvider? formatProvider, Type quantityType, string quantityString)
{
- return Default.Parse(formatProvider, quantityType, quantityString);
+ // TODO Support custom units (via the QuantityParser), currently only hardcoded built-in quantities are supported.
+ if (!typeof(IQuantity).IsAssignableFrom(quantityType))
+ throw new ArgumentException($"Type {quantityType} must be of type UnitsNet.IQuantity.");
+
+ if (TryParse(formatProvider, quantityType, quantityString, out IQuantity? quantity))
+ return quantity;
+
+ throw new UnitNotFoundException($"Quantity string '{quantityString}' could not be parsed to quantity '{quantityType}'.");
}
///
- public static bool TryParse(Type quantityType, string quantityString, [NotNullWhen(true)] out IQuantity? quantity) =>
- Default.TryParse(quantityType, quantityString, out quantity);
+ public static bool TryParse(Type quantityType, string quantityString, [NotNullWhen(true)] out IQuantity? quantity)
+ {
+ // TODO Support custom units (via the QuantityParser), currently only hardcoded built-in quantities are supported.
+ return TryParse(null, quantityType, quantityString, out quantity);
+ }
///
/// Get a list of quantities that has the given base dimensions.
@@ -212,25 +210,7 @@ public static bool TryParse(Type quantityType, string quantityString, [NotNullWh
/// The base dimensions to match.
public static IEnumerable GetQuantitiesWithBaseDimensions(BaseDimensions baseDimensions)
{
- return Default.GetQuantitiesWithBaseDimensions(baseDimensions);
- }
-
- private static List GetUnitsForAbbreviation(IFormatProvider? formatProvider, string unitAbbreviation)
- {
- // Use case-sensitive match to reduce ambiguity.
- // Don't use UnitParser.TryParse() here, since it allows case-insensitive match per quantity as long as there are no ambiguous abbreviations for
- // units of that quantity, but here we try all quantities and this results in too high of a chance for ambiguous matches,
- // such as "cm" matching both LengthUnit.Centimeter (cm) and MolarityUnit.CentimolePerLiter (cM).
- return Infos
- .SelectMany(i => i.UnitInfos)
- .Select(ui => UnitsNetSetup.Default.UnitAbbreviations
- .GetUnitAbbreviations(ui.Value.GetType(), Convert.ToInt32(ui.Value), formatProvider)
- .Contains(unitAbbreviation, StringComparer.Ordinal)
- ? ui.Value
- : null)
- .Where(unitValue => unitValue != null)
- .Select(unitValue => unitValue!)
- .ToList();
+ return Infos.GetQuantitiesWithBaseDimensions(baseDimensions);
}
}
}
diff --git a/UnitsNet/CustomCode/QuantityInfo/QuantityInfoExtensions.cs b/UnitsNet/CustomCode/QuantityInfo/QuantityInfoExtensions.cs
index 06b09a5878..8d74808c67 100644
--- a/UnitsNet/CustomCode/QuantityInfo/QuantityInfoExtensions.cs
+++ b/UnitsNet/CustomCode/QuantityInfo/QuantityInfoExtensions.cs
@@ -9,6 +9,22 @@ namespace UnitsNet;
///
internal static class QuantityInfoExtensions
{
+ ///
+ /// Get a list of quantities having the given base dimensions.
+ ///
+ /// The type of quantity mapping information.
+ /// The base dimensions to match.
+ public static IEnumerable GetQuantitiesWithBaseDimensions(this IEnumerable quantityInfos,
+ BaseDimensions baseDimensions)
+ {
+ if (baseDimensions is null)
+ {
+ throw new ArgumentNullException(nameof(baseDimensions));
+ }
+
+ return quantityInfos.Where(info => info.BaseDimensions.Equals(baseDimensions));
+ }
+
///
/// Retrieves the default unit for a specified quantity and unit system.
///
diff --git a/UnitsNet/CustomCode/UnitAbbreviationsCache.cs b/UnitsNet/CustomCode/UnitAbbreviationsCache.cs
index 6cfbff7967..bcfea9a4fe 100644
--- a/UnitsNet/CustomCode/UnitAbbreviationsCache.cs
+++ b/UnitsNet/CustomCode/UnitAbbreviationsCache.cs
@@ -4,11 +4,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Resources;
using UnitsNet.Units;
+using AbbreviationMapKey = System.ValueTuple;
// ReSharper disable once CheckNamespace
namespace UnitsNet
@@ -54,9 +54,18 @@ public UnitAbbreviationsCache()
: this(new QuantityInfoLookup([]))
{
}
-
+
+ ///
+ /// Creates an instance of the cache using the specified set of quantities.
+ ///
+ /// Instance for mapping the units of the provided quantities.
+ public UnitAbbreviationsCache(IEnumerable quantities)
+ :this(new QuantityInfoLookup(quantities))
+ {
+ }
+
///
- /// Creates an instance of the cache and load all the abbreviations defined in the library.
+ /// Creates an instance of the cache using the specified set of quantities.
///
///
/// Access type is internal until this class is matured and ready for external use.
@@ -65,12 +74,12 @@ internal UnitAbbreviationsCache(QuantityInfoLookup quantityInfoLookup)
{
QuantityInfoLookup = quantityInfoLookup;
}
-
+
///
- /// Create an instance of the cache and load all the built-in unit abbreviations defined in the library.
+ /// Create an instance of the cache and load all the built-in quantities defined in the library.
///
- /// Instance with default abbreviations cache.
- public static UnitAbbreviationsCache CreateDefault() => new(new QuantityInfoLookup(Quantity.ByName.Values));
+ /// Instance for mapping any of the built-in units.
+ public static UnitAbbreviationsCache CreateDefault() => new(new QuantityInfoLookup(Quantity.Infos));
///
/// Adds one or more unit abbreviation for the given unit enum value.
@@ -82,7 +91,7 @@ internal UnitAbbreviationsCache(QuantityInfoLookup quantityInfoLookup)
/// The type of unit enum.
public void MapUnitToAbbreviation(TUnitType unit, params string[] abbreviations) where TUnitType : struct, Enum
{
- PerformAbbreviationMapping(unit, CultureInfo.CurrentCulture, false, abbreviations);
+ PerformAbbreviationMapping(UnitKey.ForUnit(unit), CultureInfo.CurrentCulture, false, abbreviations);
}
///
@@ -95,7 +104,7 @@ public void MapUnitToAbbreviation(TUnitType unit, params string[] abb
/// The type of unit enum.
public void MapUnitToDefaultAbbreviation(TUnitType unit, string abbreviation) where TUnitType : struct, Enum
{
- PerformAbbreviationMapping(unit, CultureInfo.CurrentCulture, true, abbreviation);
+ PerformAbbreviationMapping(UnitKey.ForUnit(unit), CultureInfo.CurrentCulture, true, abbreviation);
}
///
@@ -109,7 +118,7 @@ public void MapUnitToDefaultAbbreviation(TUnitType unit, string abbre
/// The type of unit enum.
public void MapUnitToAbbreviation(TUnitType unit, IFormatProvider? formatProvider, params string[] abbreviations) where TUnitType : struct, Enum
{
- PerformAbbreviationMapping(unit, formatProvider, false, abbreviations);
+ PerformAbbreviationMapping(UnitKey.ForUnit(unit), formatProvider, false, abbreviations);
}
///
@@ -123,7 +132,7 @@ public void MapUnitToAbbreviation(TUnitType unit, IFormatProvider? fo
/// The type of unit enum.
public void MapUnitToDefaultAbbreviation(TUnitType unit, IFormatProvider? formatProvider, string abbreviation) where TUnitType : struct, Enum
{
- PerformAbbreviationMapping(unit, formatProvider, true, abbreviation);
+ PerformAbbreviationMapping(UnitKey.ForUnit(unit), formatProvider, true, abbreviation);
}
///
@@ -137,8 +146,7 @@ public void MapUnitToDefaultAbbreviation(TUnitType unit, IFormatProvi
/// Unit abbreviations to add.
public void MapUnitToAbbreviation(Type unitType, int unitValue, IFormatProvider? formatProvider, params string[] abbreviations)
{
- var enumValue = (Enum)Enum.ToObject(unitType, unitValue);
- PerformAbbreviationMapping(enumValue, formatProvider, false, abbreviations);
+ PerformAbbreviationMapping(new UnitKey(unitType, unitValue), formatProvider, false, abbreviations);
}
///
@@ -152,52 +160,58 @@ public void MapUnitToAbbreviation(Type unitType, int unitValue, IFormatProvider?
/// Unit abbreviation to add as default.
public void MapUnitToDefaultAbbreviation(Type unitType, int unitValue, IFormatProvider? formatProvider, string abbreviation)
{
- var enumValue = (Enum)Enum.ToObject(unitType, unitValue);
- PerformAbbreviationMapping(enumValue, formatProvider, true, abbreviation);
+ PerformAbbreviationMapping(new UnitKey(unitType, unitValue), formatProvider, true, abbreviation);
}
- private void PerformAbbreviationMapping(Enum unitValue, IFormatProvider? formatProvider, bool setAsDefault, params string[] abbreviations)
+ private void PerformAbbreviationMapping(UnitKey unitValue, IFormatProvider? formatProvider, bool setAsDefault, params string[] abbreviations)
{
if(!QuantityInfoLookup.TryGetUnitInfo(unitValue, out UnitInfo? unitInfo))
{
- unitInfo = new UnitInfo(unitValue, unitValue.ToString(), BaseUnits.Undefined);
- QuantityInfoLookup.AddUnitInfo(unitValue, unitInfo);
+ // TODO we should throw QuantityNotFoundException here (all QuantityInfos should be provided through the constructor)
+ unitInfo = new UnitInfo((Enum)unitValue, unitValue.ToString(), BaseUnits.Undefined);
+ QuantityInfoLookup.AddUnitInfo(unitInfo);
}
AddAbbreviation(unitInfo, formatProvider, setAsDefault, abbreviations);
}
-
+
///
- /// Gets the default abbreviation for a given unit. If a unit has more than one abbreviation defined, then it returns the first one.
- /// Example: GetDefaultAbbreviation<LengthUnit>(LengthUnit.Kilometer) => "km"
+ /// Gets the default abbreviation for a given unit type and its numeric enum value.
+ /// If a unit has more than one abbreviation defined, then it returns the first one.
+ /// Example: GetDefaultAbbreviation(LengthUnit.Centimeters, 1) => "cm"
///
/// The unit enum value.
/// The format provider to use for lookup. Defaults to if null.
/// The type of unit enum.
- /// The default unit abbreviation string.
public string GetDefaultAbbreviation(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : struct, Enum
{
- Type unitType = typeof(TUnitType);
-
- // Edge-case: If the value was cast to Enum, it still satisfies the generic constraint so we must get the type from the value instead.
- if (unitType == typeof(Enum)) unitType = unit.GetType();
-
- return GetDefaultAbbreviation(unitType, Convert.ToInt32(unit), formatProvider);
+ return GetDefaultAbbreviation(UnitKey.ForUnit(unit), formatProvider);
}
-
+
///
- /// Gets the default abbreviation for a given unit type and its numeric enum value.
- /// If a unit has more than one abbreviation defined, then it returns the first one.
- /// Example: GetDefaultAbbreviation<LengthUnit>(typeof(LengthUnit), 1) => "cm"
+ /// Gets the default abbreviation for a given unit type and its numeric enum value.
+ /// If a unit has more than one abbreviation defined, then it returns the first one.
+ /// Example: GetDefaultAbbreviation<LengthUnit>(typeof(LengthUnit), 1) => "cm"
///
/// The unit enum type.
/// The unit enum value.
/// The format provider to use for lookup. Defaults to if null.
- /// The default unit abbreviation string.
public string GetDefaultAbbreviation(Type unitType, int unitValue, IFormatProvider? formatProvider = null)
{
- var abbreviations = GetUnitAbbreviations(unitType, unitValue, formatProvider);
- return abbreviations.Length > 0 ? abbreviations[0] : string.Empty;
+ return GetDefaultAbbreviation(new UnitKey(unitType, unitValue), formatProvider);
+ }
+
+ ///
+ /// The key representing the unit type and value.
+ ///
+ /// The format provider to use for lookup. Defaults to
+ /// if null.
+ ///
+ /// The default unit abbreviation string.
+ public string GetDefaultAbbreviation(UnitKey unitKey, IFormatProvider? formatProvider = null)
+ {
+ var abbreviations = GetUnitAbbreviations(unitKey, formatProvider);
+ return abbreviations.Count > 0 ? abbreviations[0] : string.Empty;
}
///
@@ -209,7 +223,7 @@ public string GetDefaultAbbreviation(Type unitType, int unitValue, IFormatProvid
/// Unit abbreviations associated with unit.
public string[] GetUnitAbbreviations(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : struct, Enum
{
- return GetUnitAbbreviations(typeof(TUnitType), Convert.ToInt32(unit), formatProvider);
+ return GetUnitAbbreviations(UnitKey.ForUnit(unit), formatProvider).ToArray(); // TODO can we change this to return an IReadonlyCollection (as the GetAbbreviations)?
}
///
@@ -221,36 +235,37 @@ public string[] GetUnitAbbreviations(TUnitType unit, IFormatProvider?
/// Unit abbreviations associated with unit.
public string[] GetUnitAbbreviations(Type unitType, int unitValue, IFormatProvider? formatProvider = null)
{
- formatProvider ??= CultureInfo.CurrentCulture;
-
- return TryGetUnitAbbreviations(unitType, unitValue, formatProvider, out var abbreviations)
- ? abbreviations
- : throw new NotImplementedException($"No abbreviation is specified for {unitType.Name} with numeric value {unitValue}.");
+ return GetUnitAbbreviations(new UnitKey(unitType, unitValue), formatProvider).ToArray(); // TODO can we change this to return an IReadOnlyList (as the GetAbbreviations)?
+ }
+
+ ///
+ /// Retrieves the unit abbreviations for a specified unit key and optional format provider.
+ ///
+ /// The key representing the unit type and value.
+ /// An optional format provider to use for culture-specific formatting.
+ /// A read-only collection of unit abbreviation strings.
+ public IReadOnlyList GetUnitAbbreviations(UnitKey unitKey, IFormatProvider? formatProvider = null)
+ {
+ return GetAbbreviations(QuantityInfoLookup.GetUnitInfo(unitKey), formatProvider);
}
///
/// Get all abbreviations for unit.
///
- /// Enum type for unit.
- /// Enum value for unit.
+ /// The unit-enum type as a hash-friendly type.
/// The format provider to use for lookup. Defaults to if null.
/// The unit abbreviations associated with unit.
/// True if found, otherwise false.
- private bool TryGetUnitAbbreviations(Type unitType, int unitValue, IFormatProvider? formatProvider, out string[] abbreviations)
+ private bool TryGetUnitAbbreviations(UnitKey unitKey, IFormatProvider? formatProvider, out IReadOnlyList abbreviations)
{
- var name = Enum.GetName(unitType, unitValue);
- var enumInstance = (Enum)Enum.Parse(unitType, name!);
-
- if(QuantityInfoLookup.TryGetUnitInfo(enumInstance, out var unitInfo))
+ if(QuantityInfoLookup.TryGetUnitInfo(unitKey, out UnitInfo? unitInfo))
{
- abbreviations = GetAbbreviations(unitInfo, formatProvider!).ToArray();
+ abbreviations = GetAbbreviations(unitInfo, formatProvider);
return true;
}
- else
- {
- abbreviations = Array.Empty();
- return false;
- }
+
+ abbreviations = [];
+ return false;
}
///
@@ -261,28 +276,43 @@ private bool TryGetUnitAbbreviations(Type unitType, int unitValue, IFormatProvid
/// Unit abbreviations associated with unit.
public IReadOnlyList GetAllUnitAbbreviationsForQuantity(Type unitEnumType, IFormatProvider? formatProvider = null)
{
- var enumValues = Enum.GetValues(unitEnumType).Cast();
- var all = GetStringUnitPairs(enumValues, formatProvider);
- return all.Select(pair => pair.Item2).ToList();
+ var allAbbreviations = new List();
+ if (!QuantityInfoLookup.TryGetQuantityByUnitType(unitEnumType, out QuantityInfo? quantityInfo))
+ {
+ // TODO I think we should either return empty or throw QuantityNotFoundException here
+ var enumValues = Enum.GetValues(unitEnumType).Cast();
+ var all = GetStringUnitPairs(enumValues, formatProvider);
+ return all.Select(pair => pair.Item2).ToList();
+ }
+
+ foreach(UnitInfo unitInfo in quantityInfo.UnitInfos)
+ {
+ if(TryGetUnitAbbreviations(unitInfo.UnitKey, formatProvider, out IReadOnlyList abbreviations))
+ {
+ allAbbreviations.AddRange(abbreviations);
+ }
+ }
+
+ return allAbbreviations;
}
internal List<(Enum Unit, string Abbreviation)> GetStringUnitPairs(IEnumerable enumValues, IFormatProvider? formatProvider = null)
{
- var ret = new List<(Enum, string)>();
+ var unitAbbreviationsPairs = new List<(Enum, string)>();
formatProvider ??= CultureInfo.CurrentCulture;
foreach(var enumValue in enumValues)
{
- if(TryGetUnitAbbreviations(enumValue.GetType(), Convert.ToInt32(enumValue), formatProvider, out var abbreviations))
+ if(TryGetUnitAbbreviations(enumValue, formatProvider, out var abbreviations))
{
foreach(var abbrev in abbreviations)
{
- ret.Add((enumValue, abbrev));
+ unitAbbreviationsPairs.Add((enumValue, abbrev));
}
}
}
- return ret;
+ return unitAbbreviationsPairs;
}
///
@@ -321,10 +351,11 @@ public IReadOnlyList GetAbbreviations(UnitInfo unitInfo, IFormatProvider
private void AddAbbreviation(UnitInfo unitInfo, IFormatProvider? formatProvider, bool setAsDefault,
params string[] newAbbreviations)
{
- if (formatProvider is not CultureInfo)
- formatProvider = CultureInfo.CurrentCulture;
+ if (formatProvider is not CultureInfo culture)
+ {
+ culture = CultureInfo.CurrentCulture;
+ }
- var culture = (CultureInfo)formatProvider;
var cultureName = GetCultureNameOrEnglish(culture);
AbbreviationMapKey key = GetAbbreviationMapKey(unitInfo, cultureName);
@@ -360,14 +391,7 @@ private static IReadOnlyList AddAbbreviationsToList(bool setAsDefault, L
private static AbbreviationMapKey GetAbbreviationMapKey(UnitInfo unitInfo, string cultureName)
{
- // TODO Enforce quantity name for custom units, optional value was required for backwards compatibility in v5.
- // TODO Support non-enum units, using quantity name and unit name instead.
- var unitTypeName = unitInfo.Value.GetType().FullName ?? throw new InvalidOperationException("Could not resolve unit enum type name."); // .QuantityName ?? "MissingQuantityName";
-
- return new AbbreviationMapKey(
- UnitTypeName: unitTypeName,
- UnitName: unitInfo.Name,
- CultureName: cultureName);
+ return new AbbreviationMapKey(unitInfo.UnitKey, cultureName);
}
private static string GetCultureNameOrEnglish(CultureInfo culture)
@@ -395,77 +419,33 @@ private IReadOnlyList ReadAbbreviationsFromResourceFile(string? quantity
return abbreviationsList.AsReadOnly();
}
-#if NETCOREAPP
- ///
- /// Key for looking up unit abbreviations for a given unit and culture.
- ///
- ///
- /// TODO Use quantity name instead of unit enum name, as part of moving from enums to string-based lookups.
- ///
- /// The unit enum type name, such as "UnitsNet.Units.LengthUnit" or "MyApp.HowMuchUnit".
- /// The unit name, such as "Centimeter".
- /// The culture name, such as "en-US".
- [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local", Justification = "Only used for hashing and equality.")]
- private record AbbreviationMapKey(string UnitTypeName, string UnitName, string CultureName);
-#else
///
- /// Key for looking up unit abbreviations for a given unit and culture.
+ /// Retrieves a list of unit information objects that match the specified unit abbreviation.
///
+ /// An optional format provider to use for culture-specific formatting.
+ /// The unit abbreviation to search for.
+ /// A list of objects that match the specified unit abbreviation.
///
- /// TODO Use quantity name instead of unit enum name, as part of moving from enums to string-based lookups.
+ /// This method performs a case-sensitive match to reduce ambiguity. For example, "cm" could match both
+ /// LengthUnit.Centimeter (cm) and
+ /// MolarityUnit.CentimolePerLiter (cM).
///
- private class AbbreviationMapKey : IEquatable
+ internal List GetUnitsForAbbreviation(IFormatProvider? formatProvider, string unitAbbreviation)
{
- ///
- /// The unit enum type name, such as "UnitsNet.Units.LengthUnit" or "MyApp.HowMuchUnit".
- ///
- public string UnitTypeName { get; }
-
- ///
- /// The unit name, such as "Centimeter".
- ///
- public string UnitName { get; }
-
- ///
- /// The culture name, such as "en-US".
- ///
- public string CultureName { get; }
-
- [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Matches record naming.")]
- public AbbreviationMapKey(string UnitTypeName, string UnitName, string CultureName)
- {
- this.UnitTypeName = UnitTypeName;
- this.UnitName = UnitName;
- this.CultureName = CultureName;
- }
-
- public bool Equals(AbbreviationMapKey? other)
- {
- if (ReferenceEquals(null, other)) return false;
- if (ReferenceEquals(this, other)) return true;
- return UnitTypeName == other.UnitTypeName && UnitName == other.UnitName && CultureName == other.CultureName;
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != GetType()) return false;
- return Equals((AbbreviationMapKey)obj);
- }
-
- public override int GetHashCode()
- {
- unchecked
- {
- int hashCode = UnitTypeName.GetHashCode();
- hashCode = (hashCode * 397) ^ UnitName.GetHashCode();
- hashCode = (hashCode * 397) ^ CultureName.GetHashCode();
- return hashCode;
- }
- }
+ // TODO this is certain to have terrible performance (especially on the first run)
+ // TODO we should consider adding a (lazy) dictionary for these
+ // Use case-sensitive match to reduce ambiguity.
+ // Don't use UnitParser.TryParse() here, since it allows case-insensitive match per quantity as long as there are no ambiguous abbreviations for
+ // units of that quantity, but here we try all quantities and this results in too high of a chance for ambiguous matches,
+ // such as "cm" matching both LengthUnit.Centimeter (cm) and MolarityUnit.CentimolePerLiter (cM).
+ return QuantityInfoLookup.Infos
+ .SelectMany(quantityInfo => quantityInfo.UnitInfos)
+ .Select(unitInfo => GetAbbreviations(unitInfo, formatProvider).Contains(unitAbbreviation, StringComparer.Ordinal)
+ ? unitInfo
+ : null)
+ .Where(unitValue => unitValue != null)
+ .Select(unitValue => unitValue!)
+ .ToList();
}
-#endif
-
}
}
diff --git a/UnitsNet/CustomCode/UnitKey.cs b/UnitsNet/CustomCode/UnitKey.cs
new file mode 100644
index 0000000000..f759dac2a0
--- /dev/null
+++ b/UnitsNet/CustomCode/UnitKey.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+// ReSharper disable ConvertToAutoPropertyWhenPossible
+
+namespace UnitsNet;
+
+///
+/// Represents a unique key for a unit type and its corresponding value.
+///
+///
+/// This key is particularly useful when using an enum-based unit in a hash-based collection,
+/// as it avoids the boxing that would normally occur when casting the enum to .
+///
+[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
+public readonly record struct UnitKey
+{
+ // apparently, on netstandard, the use of auto-properties is significantly slower
+ private readonly Type _unitType;
+ private readonly int _unitValue;
+
+ ///
+ /// Represents a unique key for a unit type and its corresponding value.
+ ///
+ ///
+ /// This key is particularly useful when using an enum-based unit in a hash-based collection,
+ /// as it avoids the boxing that would normally occur when casting the enum to .
+ ///
+ public UnitKey(Type UnitType, int UnitValue)
+ {
+ _unitType = UnitType;
+ _unitValue = UnitValue;
+ }
+
+ ///
+ /// Gets the type of the unit represented by this .
+ ///
+ ///
+ /// This property holds the of the unit enumeration associated with this key.
+ /// It is particularly useful for identifying the unit type in scenarios where multiple unit types are used.
+ ///
+ public Type UnitType
+ {
+ get => _unitType;
+ }
+
+ ///
+ /// Gets the integer value associated with the unit type.
+ ///
+ ///
+ /// This property represents the unique value of the unit within its type, typically corresponding to the underlying
+ /// integer value of an enumeration.
+ ///
+ public int UnitValue
+ {
+ get => _unitValue;
+ }
+
+ ///
+ /// Creates a new instance of the struct for the specified unit.
+ ///
+ /// The type of the unit, which must be a struct and an enumeration.
+ /// The unit value to create the for.
+ /// A new instance of the struct representing the specified unit.
+ public static UnitKey ForUnit(TUnit unit)
+ where TUnit : struct, Enum
+ {
+ return new UnitKey(typeof(TUnit), Unsafe.As(ref unit));
+ }
+
+ ///
+ /// Creates a new instance of the struct for a specified unit type and value.
+ ///
+ /// The type of the unit, which must be an enumeration.
+ /// The integer value representing the unit.
+ /// A new instance representing the specified unit type and value.
+ public static UnitKey Create(int unitValue)
+ where TUnit : struct, Enum
+ {
+ return new UnitKey(typeof(TUnit), unitValue);
+ }
+
+ ///
+ /// Implicitly converts an enumeration value to a .
+ ///
+ /// The enumeration value to convert.
+ /// A new instance of the struct representing the specified enumeration value.
+ ///
+ /// This implicit conversion allows for seamless usage of enumeration values where instances are
+ /// expected.
+ ///
+ /// For better performance, prefer using the method, which avoids the boxing
+ /// involved with the cast to .
+ ///
+ ///
+ public static implicit operator UnitKey(Enum unit)
+ {
+ // using Unsafe.Unbox(unit) isn't any faster
+ return new UnitKey(unit.GetType(), (int)(object)unit);
+ }
+
+ ///
+ /// Explicitly converts a to its corresponding enumeration value.
+ ///
+ /// The instance to convert.
+ /// The enumeration value represented by the .
+ ///
+ /// This explicit conversion is useful when you need to retrieve the original enumeration value from a
+ /// .
+ ///
+ public static explicit operator Enum(UnitKey unitKey)
+ {
+ return (Enum)Enum.ToObject(unitKey._unitType, unitKey._unitValue);
+ }
+
+ ///
+ /// Converts the current to its corresponding enumeration value of type
+ /// .
+ ///
+ /// The type of the unit, which must be a struct and an enumeration.
+ /// The enumeration value of type represented by the current .
+ ///
+ /// Thrown when the type of does not match the type of the current
+ /// .
+ ///
+ ///
+ /// This method is useful for retrieving the original enumeration value from a .
+ ///
+ public TUnit ToUnit() where TUnit : struct, Enum
+ {
+ if (typeof(TUnit) != _unitType)
+ {
+ throw new InvalidOperationException($"Cannot convert UnitKey of type {_unitType} to {typeof(TUnit)}.");
+ }
+
+ var unitValue = _unitValue;
+ return Unsafe.As(ref unitValue);
+ }
+
+ private string GetDebuggerDisplay()
+ {
+ try
+ {
+ var unitName = Enum.GetName(_unitType, _unitValue);
+ return string.IsNullOrEmpty(unitName) ? $"{nameof(UnitType)}: {_unitType}, {nameof(UnitValue)} = {_unitValue}" : $"{_unitType.Name}.{unitName}";
+ }
+ catch
+ {
+ return $"{nameof(UnitType)}: {_unitType}, {nameof(UnitValue)} = {_unitValue}";
+ }
+ }
+
+ ///
+ /// Deconstructs the into its component parts.
+ ///
+ /// The type of the unit.
+ /// The value of the unit.
+ ///
+ /// This method allows for the use of deconstruction syntax to extract the unit type and value
+ /// from a instance.
+ ///
+ public void Deconstruct(out Type unitType, out int unitValue)
+ {
+ unitType = _unitType;
+ unitValue = _unitValue;
+ }
+
+ #region Equality members
+
+ ///
+ public bool Equals(UnitKey other)
+ {
+ // implementing the Equality members on net48 is 5x faster than the default
+ return _unitType == other._unitType && _unitValue == other._unitValue;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ // implementing the Equality members on net48 is 5x faster than the default
+ if (_unitType == null)
+ {
+ return _unitValue;
+ }
+
+ unchecked
+ {
+ return (_unitType.GetHashCode() * 397) ^ _unitValue;
+ }
+ }
+
+ #endregion
+}
diff --git a/UnitsNet/CustomCode/UnitParser.cs b/UnitsNet/CustomCode/UnitParser.cs
index 77f96b1018..53d63bcfa5 100644
--- a/UnitsNet/CustomCode/UnitParser.cs
+++ b/UnitsNet/CustomCode/UnitParser.cs
@@ -237,5 +237,77 @@ public bool TryParse([NotNullWhen(true)] string? unitAbbreviation, Type unitType
(Enum Unit, string Abbreviation)[] caseSensitiveMatches = stringUnitPairs.Where(pair => pair.Abbreviation.Equals(unitAbbreviation)).ToArray();
return caseSensitiveMatches.Length == 0 ? matches : caseSensitiveMatches;
}
+
+ ///
+ /// Retrieves the unit information from the given unit abbreviation.
+ ///
+ ///
+ /// This method is currently not optimized for performance and will enumerate all units and their unit abbreviations
+ /// each time.
+ /// Unit abbreviation matching in the
+ /// overload is case-insensitive.
+ ///
+ /// This will fail if more than one unit across all quantities share the same unit abbreviation.
+ ///
+ /// The unit abbreviation to parse.
+ /// The format provider to use for culture-specific formatting. Can be null.
+ /// The unit information corresponding to the given unit abbreviation.
+ ///
+ /// Thrown when the unit abbreviation is not recognized as a valid unit for the specified culture.
+ ///
+ ///
+ /// Thrown when multiple units are found matching the given unit abbreviation.
+ ///
+ internal UnitInfo GetUnitFromAbbreviation(string unitAbbreviation, IFormatProvider? formatProvider)
+ {
+ List units = _unitAbbreviationsCache.GetUnitsForAbbreviation(formatProvider, unitAbbreviation);
+ return units.Count switch
+ {
+ 0 => throw new UnitNotFoundException(
+ $"The unit abbreviation '{unitAbbreviation}' is not recognized as a valid unit for the specified culture."),
+ 1 => units[0],
+ _ => throw new AmbiguousUnitParseException(
+ $"Cannot parse \"{unitAbbreviation}\" since it matches multiple units: {string.Join(", ", units.Select(x => x.Name).OrderBy(x => x))}.")
+ };
+ }
+
+ ///
+ /// Attempts to parse the specified unit abbreviation into an object.
+ ///
+ ///
+ /// This method is currently not optimized for performance and will enumerate all units and their unit abbreviations
+ /// each time.
+ /// Unit abbreviation matching in the
+ /// overload is case-insensitive.
+ ///
+ /// This will fail if more than one unit across all quantities share the same unit abbreviation.
+ ///
+ /// The unit abbreviation to parse.
+ /// The format provider to use for parsing, or null to use the current culture.
+ ///
+ /// When this method returns, contains the parsed object if the parsing succeeded,
+ /// or null if the parsing failed. This parameter is passed uninitialized.
+ ///
+ ///
+ /// true if the unit abbreviation was successfully parsed; otherwise, false.
+ ///
+ internal bool TryGetUnitFromAbbreviation([NotNullWhen(true)]string? unitAbbreviation, IFormatProvider? formatProvider, [NotNullWhen(true)] out UnitInfo? unit)
+ {
+ if (unitAbbreviation == null)
+ {
+ unit = null;
+ return false;
+ }
+
+ List units = _unitAbbreviationsCache.GetUnitsForAbbreviation(formatProvider, unitAbbreviation);
+ if (units.Count == 1)
+ {
+ unit = units[0];
+ return true;
+ }
+
+ unit = null;
+ return false;
+ }
}
}
diff --git a/UnitsNet/CustomCode/UnitsNetSetup.cs b/UnitsNet/CustomCode/UnitsNetSetup.cs
index aa6f22f9f0..76ac4c7d26 100644
--- a/UnitsNet/CustomCode/UnitsNetSetup.cs
+++ b/UnitsNet/CustomCode/UnitsNetSetup.cs
@@ -2,6 +2,7 @@
// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet.
using System.Collections.Generic;
+using System.Linq;
using UnitsNet.Units;
namespace UnitsNet;
@@ -20,7 +21,7 @@ public sealed class UnitsNetSetup
static UnitsNetSetup()
{
var unitConverter = UnitConverter.CreateDefault();
- ICollection quantityInfos = Quantity.ByName.Values;
+ IReadOnlyCollection quantityInfos = Quantity.ByName.Values.ToList();
Default = new UnitsNetSetup(quantityInfos, unitConverter);
}
@@ -30,7 +31,7 @@ static UnitsNetSetup()
///
/// The quantities and their units to support for unit conversions, Parse() and ToString().
/// The unit converter instance.
- public UnitsNetSetup(ICollection quantityInfos, UnitConverter unitConverter)
+ public UnitsNetSetup(IEnumerable quantityInfos, UnitConverter unitConverter)
{
var quantityInfoLookup = new QuantityInfoLookup(quantityInfos);
var unitAbbreviations = new UnitAbbreviationsCache(quantityInfoLookup);
diff --git a/UnitsNet/InternalHelpers/CultureHelper.cs b/UnitsNet/InternalHelpers/CultureHelper.cs
index d8c40745c8..37931f6e88 100644
--- a/UnitsNet/InternalHelpers/CultureHelper.cs
+++ b/UnitsNet/InternalHelpers/CultureHelper.cs
@@ -1,4 +1,4 @@
-// Licensed under MIT No Attribution, see LICENSE file at the root.
+// 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;
@@ -10,6 +10,7 @@ namespace UnitsNet.InternalHelpers;
///
/// Helper class for and related operations.
///
+[Obsolete("string -> CultureInfo conversions are not in the scope of UnitsNet")]
internal static class CultureHelper
{
private static readonly ConcurrentDictionary CultureCache = new();
diff --git a/UnitsNet/QuantityDisplay.cs b/UnitsNet/QuantityDisplay.cs
index 71c0d25ec5..166afc9544 100644
--- a/UnitsNet/QuantityDisplay.cs
+++ b/UnitsNet/QuantityDisplay.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
@@ -33,29 +34,28 @@ public AbbreviationDisplay(IQuantity quantity)
_quantity = quantity;
QuantityInfo quantityQuantityInfo = quantity.QuantityInfo;
IQuantity baseQuantity = quantity.ToUnit(quantityQuantityInfo.BaseUnitInfo.Value);
- Conversions = quantityQuantityInfo.UnitInfos.Select(x => new ConvertedQuantity(baseQuantity, x.Value)).ToArray();
+ Conversions = quantityQuantityInfo.UnitInfos.Select(x => new ConvertedQuantity(baseQuantity, x)).ToArray();
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- public string DefaultAbbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(_quantity.Unit.GetType(), Convert.ToInt32(_quantity.Unit));
+ public string DefaultAbbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(_quantity.Unit);
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
- public string[] Abbreviations =>
- UnitsNetSetup.Default.UnitAbbreviations.GetUnitAbbreviations(_quantity.QuantityInfo.UnitType, Convert.ToInt32(_quantity.Unit));
+ public IReadOnlyList Abbreviations => UnitsNetSetup.Default.UnitAbbreviations.GetUnitAbbreviations(_quantity.Unit);
public ConvertedQuantity[] Conversions { get; }
[DebuggerDisplay("{Abbreviation}")]
- internal readonly struct ConvertedQuantity(IQuantity baseQuantity, Enum unit)
+ internal readonly struct ConvertedQuantity(IQuantity baseQuantity, UnitInfo unit)
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- public Enum Unit { get; } = unit;
+ public UnitInfo Unit { get; } = unit;
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
- public IQuantity Quantity => baseQuantity.ToUnit(Unit);
+ public IQuantity Quantity => baseQuantity.ToUnit(Unit.Value);
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- public string Abbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(Unit.GetType(), Convert.ToInt32(Unit));
+ public string Abbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(Unit.UnitKey);
public override string ToString()
{
@@ -112,7 +112,7 @@ public QuantityConvertor(IQuantity quantity)
QuantityToString = new StringFormatsDisplay(quantity);
QuantityInfo quantityQuantityInfo = quantity.QuantityInfo;
IQuantity baseQuantity = quantity.ToUnit(quantityQuantityInfo.BaseUnitInfo.Value);
- QuantityToUnit = quantityQuantityInfo.UnitInfos.Select(x => new ConvertedQuantity(baseQuantity.ToUnit(x.Value))).ToArray();
+ QuantityToUnit = quantityQuantityInfo.UnitInfos.Select(x => new ConvertedQuantity(baseQuantity.ToUnit(x.Value), x)).ToArray();
}
public StringFormatsDisplay QuantityToString { get; }
@@ -126,10 +126,10 @@ internal readonly struct StringFormatsDisplay(IQuantity quantity)
}
[DebuggerDisplay("{Quantity}")]
- internal readonly struct ConvertedQuantity(IQuantity quantity)
+ internal readonly struct ConvertedQuantity(IQuantity quantity, UnitInfo unitInfo)
{
- public Enum Unit => Quantity.Unit;
- public string Abbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(Quantity.Unit.GetType(), Convert.ToInt32(Quantity.Unit));
+ public UnitInfo Unit { get; } = unitInfo;
+ public string Abbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(Unit.UnitKey);
public ValueDisplay Value => new(Quantity);
public IQuantity Quantity { get; } = quantity;
diff --git a/UnitsNet/QuantityInfo.cs b/UnitsNet/QuantityInfo.cs
index 6e9caa78fa..d50bf8ce69 100644
--- a/UnitsNet/QuantityInfo.cs
+++ b/UnitsNet/QuantityInfo.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using UnitsNet.Units;
@@ -41,7 +42,7 @@ public QuantityInfo(string name, Type unitType, UnitInfo[] unitInfos, Enum baseU
UnitInfos = unitInfos ?? throw new ArgumentNullException(nameof(unitInfos));
BaseUnitInfo = UnitInfos.First(unitInfo => unitInfo.Value.Equals(baseUnit));
- ValueType = zero.GetType();
+ QuantityType = zero.GetType();
}
///
@@ -68,11 +69,19 @@ public QuantityInfo(string name, Type unitType, UnitInfo[] unitInfos, Enum baseU
/// Unit enum type, such as or .
///
public Type UnitType { get; }
-
+
///
- /// Quantity value type, such as or .
+ /// Quantity value type, such as or .
///
- public Type ValueType { get; }
+ public Type QuantityType { get; }
+
+ ///
+ [Obsolete("Replaced by the QuantityType property.")]
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ public Type ValueType
+ {
+ get => QuantityType;
+ }
///
/// The for a quantity.
diff --git a/UnitsNet/QuantityInfoLookup.cs b/UnitsNet/QuantityInfoLookup.cs
index 386e797cf0..2c3dc302ad 100644
--- a/UnitsNet/QuantityInfoLookup.cs
+++ b/UnitsNet/QuantityInfoLookup.cs
@@ -1,168 +1,261 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
using System.Linq;
+#if NET8_0_OR_GREATER
+using System.Collections.Frozen;
+using QuantityByTypeLookupDictionary = System.Collections.Frozen.FrozenDictionary;
+using QuantityByNameLookupDictionary = System.Collections.Frozen.FrozenDictionary;
+#else
+using QuantityByTypeLookupDictionary = System.Collections.Generic.Dictionary;
+using QuantityByNameLookupDictionary = System.Collections.Generic.Dictionary;
+#endif
+using UnitByKeyLookupDictionary = System.Collections.Generic.Dictionary;
-namespace UnitsNet
+namespace UnitsNet;
+
+///
+/// A collection of .
+///
+///
+/// Access type is internal until this class is matured and ready for external use.
+///
+internal class QuantityInfoLookup
{
+ private readonly QuantityInfo[] _quantities;
+ private readonly Lazy _quantitiesByName;
+ private readonly Lazy _quantitiesByUnitType;
+ private readonly Lazy _unitsByKey;
+
+ private QuantityByNameLookupDictionary GroupQuantitiesByName()
+ {
+#if NET8_0_OR_GREATER
+ return _quantities.ToFrozenDictionary(info => info.Name, StringComparer.OrdinalIgnoreCase);
+#else
+ return _quantities.ToDictionary(info => info.Name, StringComparer.OrdinalIgnoreCase);
+#endif
+ }
+
+ private QuantityByTypeLookupDictionary GroupQuantitiesByUnitType()
+ {
+#if NET8_0_OR_GREATER
+ return _quantities.ToFrozenDictionary(info => info.UnitType);
+#else
+ return _quantities.ToDictionary(info => info.UnitType);
+#endif
+ }
+
+ private UnitByKeyLookupDictionary GroupUnitsByKey()
+ {
+ return _quantities.SelectMany(quantityInfo => quantityInfo.UnitInfos).ToDictionary(x => x.UnitKey);
+ }
+
///
- /// A collection of .
+ /// Initializes a new instance of the class.
///
+ ///
+ /// A collection of objects representing the quantities to be managed by this lookup.
+ ///
+ ///
+ /// Thrown when the parameter is null.
+ ///
///
- /// Access type is internal until this class is matured and ready for external use.
+ /// This constructor organizes the provided quantity information into internal lookup structures
+ /// for efficient access by name, unit type, and unit key.
///
- internal class QuantityInfoLookup
+ public QuantityInfoLookup(IEnumerable quantityInfos)
{
- private readonly Lazy _infosLazy;
- private readonly Lazy> _unitTypeAndNameToUnitInfoLazy;
-
- ///
- /// New instance.
- ///
- ///
- public QuantityInfoLookup(ICollection quantityInfos)
- {
- Names = quantityInfos.Select(qt => qt.Name).ToArray();
-
- _infosLazy = new Lazy(() => quantityInfos
- .OrderBy(quantityInfo => quantityInfo.Name)
- .ToArray());
+ _quantities = quantityInfos.ToArray();
+ _quantitiesByName = new Lazy(GroupQuantitiesByName);
+ _quantitiesByUnitType = new Lazy(GroupQuantitiesByUnitType);
+ _unitsByKey = new Lazy(GroupUnitsByKey);
+ }
- _unitTypeAndNameToUnitInfoLazy = new Lazy>(() =>
- {
- return Infos
- .SelectMany(quantityInfo => quantityInfo.UnitInfos
- .Select(unitInfo => new KeyValuePair<(Type, string), UnitInfo>(
- (unitInfo.Value.GetType(), unitInfo.Name),
- unitInfo)))
- .ToDictionary(x => x.Key, x => x.Value);
- });
- }
+ ///
+ /// All enum value names of , such as "Length" and "Mass".
+ ///
+ public IReadOnlyCollection Names => _quantitiesByName.Value.Keys;
- ///
- /// All enum value names of , such as "Length" and "Mass".
- ///
- public string[] Names { get; }
+ ///
+ /// A read-only dictionary that maps quantity names to their corresponding .
+ ///
+ public IReadOnlyDictionary ByName => _quantitiesByName.Value;
- ///
- /// All quantity information objects, such as and .
- ///
- public QuantityInfo[] Infos => _infosLazy.Value;
+ ///
+ /// All quantity information objects, such as and .
+ ///
+ public IReadOnlyList Infos => _quantities;
- ///
- /// Gets the for a given unit.
- ///
- public QuantityInfo GetQuantityInfo(UnitInfo unitInfo)
+ ///
+ /// Retrieves the for a specified .
+ ///
+ /// The key representing the unit for which information is being requested.
+ /// The associated with the specified .
+ ///
+ /// Thrown when no unit information is found for the specified
+ /// .
+ ///
+ public UnitInfo GetUnitInfo(UnitKey unitKey)
+ {
+ if (!TryGetUnitInfo(unitKey, out UnitInfo? unitInfo))
{
- Type unitType = unitInfo.Value.GetType();
- return _infosLazy.Value.First(i => i.UnitType == unitType);
+ throw new UnitNotFoundException($"No unit information found for the specified enum value: {unitKey}.");
}
- ///
- /// Try to get the for a given unit.
- ///
- public bool TryGetQuantityInfo(UnitInfo unitInfo, [NotNullWhen(true)] out QuantityInfo? quantityInfo)
- {
- Type unitType = unitInfo.Value.GetType();
- if (_infosLazy.Value.FirstOrDefault(i => i.UnitType == unitType) is { } qi)
- {
- quantityInfo = qi;
- return true;
- }
+ return unitInfo;
+ }
- quantityInfo = default;
- return false;
- }
+ ///
+ /// Try to get for a given unit enum value.
+ ///
+ public bool TryGetUnitInfo(UnitKey unitKey, [NotNullWhen(true)] out UnitInfo? unitInfo)
+ {
+ return _unitsByKey.Value.TryGetValue(unitKey, out unitInfo);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void AddUnitInfo(UnitInfo unitInfo)
+ {
+ _unitsByKey.Value.Add(unitInfo.UnitKey, unitInfo);
+ }
+
+ ///
+ /// Dynamically construct a quantity.
+ ///
+ /// Numeric value.
+ /// Unit enum value.
+ /// An object.
+ /// Unit value is not a know unit enum type.
+ public IQuantity From(double value, UnitKey unit)
+ {
+ // TODO Support custom units, currently only hardcoded built-in quantities are supported.
+ return Quantity.TryFrom(value, (Enum)unit, out IQuantity? quantity)
+ ? quantity
+ : throw new UnitNotFoundException($"Unit value {unit} of type {unit.GetType()} is not a known unit enum type. Expected types like UnitsNet.Units.LengthUnit. Did you pass in a custom enum type defined outside the UnitsNet library?");
+ }
+
+ ///
+ /// Attempts to create a quantity from the specified value and unit.
+ ///
+ /// The value of the quantity.
+ /// The unit of the quantity, represented as an .
+ ///
+ /// When this method returns, contains the created quantity if the conversion succeeded,
+ /// or null if the conversion failed. This parameter is passed uninitialized.
+ ///
+ ///
+ /// true if the quantity was successfully created; otherwise, false.
+ ///
+ public bool TryFrom(double value, [NotNullWhen(true)] Enum? unit, [NotNullWhen(true)] out IQuantity? quantity)
+ {
+ // TODO Support custom units, currently only hardcoded built-in quantities are supported.
+ return Quantity.TryFrom(value, unit, out quantity);
+ }
- ///
- /// Get for a given unit enum value.
- ///
- public UnitInfo GetUnitInfo(Enum unitEnum) => _unitTypeAndNameToUnitInfoLazy.Value[(unitEnum.GetType(), unitEnum.ToString())];
-
- ///
- /// Try to get for a given unit enum value.
- ///
- public bool TryGetUnitInfo(Enum unitEnum, [NotNullWhen(true)] out UnitInfo? unitInfo) =>
- _unitTypeAndNameToUnitInfoLazy.Value.TryGetValue((unitEnum.GetType(), unitEnum.ToString()), out unitInfo);
-
- ///
- ///
- ///
- ///
- ///
- public void AddUnitInfo(Enum unit, UnitInfo unitInfo)
+ ///
+ /// Retrieves the associated with the specified quantity name.
+ ///
+ /// The name of the quantity to retrieve information for.
+ /// The associated with the specified quantity name.
+ ///
+ /// Thrown when no quantity information is found for the specified quantity name.
+ ///
+ internal QuantityInfo GetQuantityByName(string quantityName)
+ {
+ if (!ByName.TryGetValue(quantityName, out QuantityInfo? quantityInfo))
{
- _unitTypeAndNameToUnitInfoLazy.Value.Add((unit.GetType(), unit.ToString()), unitInfo);
+ throw new QuantityNotFoundException($"No quantity information was found for the type: {quantityName}.")
+ {
+ Data = { ["quantityName"] = quantityName }
+ };
}
- ///
- /// Dynamically construct a quantity.
- ///
- /// Numeric value.
- /// Unit enum value.
- /// An object.
- /// Unit value is not a know unit enum type.
- public IQuantity From(double value, Enum unit)
- {
- // TODO Support custom units, currently only hardcoded built-in quantities are supported.
- return Quantity.TryFrom(value, unit, out IQuantity? quantity)
- ? quantity
- : throw new UnitNotFoundException($"Unit value {unit} of type {unit.GetType()} is not a known unit enum type. Expected types like UnitsNet.Units.LengthUnit. Did you pass in a custom enum type defined outside the UnitsNet library?");
- }
+ return quantityInfo;
+ }
- ///
- public bool TryFrom(double value, Enum unit, [NotNullWhen(true)] out IQuantity? quantity)
- {
- // Implicit cast to QuantityValue would prevent TryFrom from being called,
- // so we need to explicitly check this here for double arguments.
- if (double.IsNaN(value) || double.IsInfinity(value))
- {
- quantity = default(IQuantity);
- return false;
- }
+ ///
+ /// Attempts to retrieve the associated with the specified quantity name.
+ ///
+ /// The name of the quantity to look up.
+ ///
+ /// When this method returns, contains the associated with the specified quantity name,
+ /// if the name is found; otherwise, null. This parameter is passed uninitialized.
+ ///
+ ///
+ /// true if the quantity name was found; otherwise, false.
+ ///
+ internal bool TryGetQuantityByName(string quantityName, [NotNullWhen(true)] out QuantityInfo? quantityInfo)
+ {
+ return ByName.TryGetValue(quantityName, out quantityInfo);
+ }
- // TODO Support custom units, currently only hardcoded built-in quantities are supported.
- return Quantity.TryFrom(value, unit, out quantity);
+ ///
+ /// Attempts to parse a unit information object based on its quantity and unit names.
+ ///
+ ///
+ /// The invariant quantity name, such as "Length". This parameter does not support localization.
+ ///
+ ///
+ /// The invariant unit enum name, such as "Meter". This parameter does not support localization.
+ ///
+ ///
+ /// The object representing the unit information.
+ ///
+ ///
+ /// Thrown when no quantity information is found for the specified quantity name.
+ ///
+ ///
+ /// Thrown when no unit is found for the specified quantity name and unit name.
+ ///
+ internal UnitInfo GetUnitByName(string quantityName, string unitName)
+ {
+ QuantityInfo quantityInfo = GetQuantityByName(quantityName);
+ UnitInfo? unitInfo = quantityInfo.UnitInfos.FirstOrDefault(unit => string.Equals(unit.Name, unitName, StringComparison.OrdinalIgnoreCase));
+ return unitInfo ??
+ throw new UnitNotFoundException($"No unit was found for quantity '{quantityName}' with the name: '{unitName}'.")
+ {
+ Data = { ["quantityName"] = quantityName, ["unitName"] = unitName }
+ };
+ }
+
+ ///
+ /// Attempts to parse unit information based on its quantity and unit names.
+ ///
+ /// The invariant quantity name, such as "Length". This parameter does not support localization.
+ /// The invariant unit name, such as "Meter". This parameter does not support localization.
+ ///
+ /// When this method returns, contains the parsed unit information if the parsing succeeded, or null if the
+ /// parsing failed.
+ ///
+ /// true if the unit information was successfully parsed; otherwise, false.
+ internal bool TryGetUnitByName(string quantityName, string unitName, [NotNullWhen(true)] out UnitInfo? unitInfo)
+ {
+ if (!TryGetQuantityByName(quantityName, out QuantityInfo? quantityInfo))
+ {
+ unitInfo = null;
+ return false;
}
- ///
- public IQuantity Parse(Type quantityType, string quantityString) => Parse(null, quantityType, quantityString);
-
- ///
- /// Dynamically parse a quantity string representation.
- ///
- /// The format provider to use for lookup. Defaults to if null.
- /// Type of quantity, such as .
- /// Quantity string representation, such as "1.5 kg". Must be compatible with given quantity type.
- /// The parsed quantity.
- /// Type must be of type UnitsNet.IQuantity -or- Type is not a known quantity type.
- public IQuantity Parse(IFormatProvider? formatProvider, Type quantityType, string quantityString)
- {
- if (!typeof(IQuantity).IsAssignableFrom(quantityType))
- throw new ArgumentException($"Type {quantityType} must be of type UnitsNet.IQuantity.");
+ unitInfo = quantityInfo.UnitInfos.FirstOrDefault(unit => string.Equals(unit.Name, unitName, StringComparison.OrdinalIgnoreCase));
+ return unitInfo is not null;
+ }
- // TODO Support custom units, currently only hardcoded built-in quantities are supported.
- if (Quantity.TryParse(formatProvider, quantityType, quantityString, out IQuantity? quantity))
- return quantity;
- throw new UnitNotFoundException($"Quantity string '{quantityString}' could not be parsed to quantity '{quantityType}'.");
- }
+ public bool TryGetQuantityByUnitType(Type unitType, [NotNullWhen(true)] out QuantityInfo? quantityInfo)
+ {
+ return _quantitiesByUnitType.Value.TryGetValue(unitType, out quantityInfo);
+ }
- ///
- public bool TryParse(Type quantityType, string quantityString, [NotNullWhen(true)] out IQuantity? quantity)
+ public QuantityInfo GetQuantityByUnitType(Type unitType)
+ {
+ if (TryGetQuantityByUnitType(unitType, out QuantityInfo? quantityInfo))
{
- // TODO Support custom units, currently only hardcoded built-in quantities are supported.
- return Quantity.TryParse(null, quantityType, quantityString, out quantity);
+ return quantityInfo;
}
- ///
- /// Get a list of quantities that has the given base dimensions.
- ///
- /// The base dimensions to match.
- public IEnumerable GetQuantitiesWithBaseDimensions(BaseDimensions baseDimensions)
- {
- return _infosLazy.Value.Where(info => info.BaseDimensions.Equals(baseDimensions));
- }
+ throw new UnitNotFoundException($"No quantity was found with the specified unit type: '{unitType}'.") { Data = { ["unitType"] = unitType.Name } };
}
}
diff --git a/UnitsNet/QuantityNotFoundException.cs b/UnitsNet/QuantityNotFoundException.cs
new file mode 100644
index 0000000000..dbd088b93c
--- /dev/null
+++ b/UnitsNet/QuantityNotFoundException.cs
@@ -0,0 +1,33 @@
+// 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;
+
+namespace UnitsNet;
+
+///
+/// Represents an exception that is thrown when a quantity is not found.
+///
+///
+/// This exception is typically encountered during dynamic conversions, such as when using
+/// to convert units by their names.
+///
+public class QuantityNotFoundException : UnitsNetException
+{
+ ///
+ public QuantityNotFoundException()
+ {
+ }
+
+ ///
+ public QuantityNotFoundException(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ public QuantityNotFoundException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+}
diff --git a/UnitsNet/UnitConverter.cs b/UnitsNet/UnitConverter.cs
index 5a8c1e917f..0815506966 100644
--- a/UnitsNet/UnitConverter.cs
+++ b/UnitsNet/UnitConverter.cs
@@ -5,7 +5,6 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
-using System.Linq;
using UnitsNet.InternalHelpers;
using UnitsNet.Units;
@@ -321,24 +320,25 @@ public static bool TryConvert(double fromValue, Enum fromUnitValue, Enum toUnitV
/// c) To unit: Meter, Centimeter etc if Length is selected
///
///
- /// Input value, which together with represents the quantity to
+ /// 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.
+ /// 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 .
- /// No units match the abbreviation.
+ /// 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 static double ConvertByName(double fromValue, string quantityName, string fromUnit, string toUnit)
+ public static double ConvertByName(double fromValue, string quantityName, string fromUnitName, string toUnitName)
{
- if (!TryParseUnit(quantityName, toUnit, out Enum? toUnitValue)) // ex: LengthUnit.Centimeter
- {
- throw new UnitNotFoundException($"Unit not found [{toUnit}] for quantity [{quantityName}].") { Data = { ["unitName"] = toUnit } };
- }
-
- return Quantity.From(fromValue, quantityName, fromUnit).As(toUnitValue);
+ QuantityInfoLookup quantities = UnitsNetSetup.Default.QuantityInfoLookup;
+ UnitInfo fromUnit = quantities.GetUnitByName(quantityName, fromUnitName);
+ UnitInfo toUnit = quantities.GetUnitByName(quantityName, toUnitName);
+ return Quantity.From(fromValue, fromUnit.Value).As(toUnit.Value);
}
///
@@ -361,22 +361,17 @@ public static double ConvertByName(double fromValue, string quantityName, string
/// True if conversion was successful.
public static bool TryConvertByName(double inputValue, string quantityName, string fromUnit, string toUnit, out double result)
{
+ QuantityInfoLookup quantities = UnitsNetSetup.Default.QuantityInfoLookup;
+ if (quantities.TryGetUnitByName(quantityName, fromUnit, out UnitInfo? fromUnitInfo) &&
+ quantities.TryGetUnitByName(quantityName, toUnit, out UnitInfo? toUnitInfo) &&
+ Quantity.TryFrom(inputValue, fromUnitInfo.Value, out IQuantity? quantity))
+ {
+ result = quantity.As(toUnitInfo.Value);
+ return true;
+ }
+
result = 0d;
-
- if (!TryGetUnitType(quantityName, out Type? unitType))
- return false;
-
- if (!TryParseUnit(unitType, fromUnit, out Enum? fromUnitValue)) // ex: LengthUnit.Meter
- return false;
-
- if (!TryParseUnit(unitType, toUnit, out Enum? toUnitValue)) // ex: LengthUnit.Centimeter
- return false;
-
- if (!Quantity.TryFrom(inputValue, fromUnitValue, out IQuantity? quantity))
- return false;
-
- result = quantity.As(toUnitValue);
- return true;
+ return false;
}
///
@@ -396,9 +391,14 @@ public static bool TryConvertByName(double inputValue, string quantityName, stri
/// 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 static double ConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev)
{
- return ConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, null);
+ return ConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, (IFormatProvider?)null);
}
///
@@ -419,23 +419,51 @@ public static double ConvertByAbbreviation(double fromValue, string quantityName
/// Culture to parse abbreviations with.
/// double centimeters = ConvertByName(5, "Length", "m", "cm"); // 500
/// Output value as the result of converting to .
- ///
- /// No unit types match the prefix of or no units
- /// are mapped to the abbreviation.
+ ///
+ /// 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 the IFormatProvider.")]
public static double ConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, string? culture)
{
- if (!TryGetUnitType(quantityName, out Type? unitType))
- throw new UnitNotFoundException($"The unit type for the given quantity was not found: {quantityName}");
-
- var cultureInfo = CultureHelper.GetCultureOrInvariant(culture);
-
- var fromUnit = UnitsNetSetup.Default.UnitParser.Parse(fromUnitAbbrev, unitType, cultureInfo); // ex: ("m", LengthUnit) => LengthUnit.Meter
- var fromQuantity = Quantity.From(fromValue, fromUnit);
+ return ConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, CultureHelper.GetCultureOrInvariant(culture));
+ }
- var toUnit = UnitsNetSetup.Default.UnitParser.Parse(toUnitAbbrev, unitType, cultureInfo); // ex:("cm", LengthUnit) => LengthUnit.Centimeter
- return fromQuantity.As(toUnit);
+ ///
+ /// 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 format provider to use for lookup. 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 static double ConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, IFormatProvider? formatProvider)
+ {
+ QuantityInfoLookup quantities = UnitsNetSetup.Default.QuantityInfoLookup;
+ UnitParser unitParser = UnitsNetSetup.Default.UnitParser;
+ QuantityInfo quantityInfo = quantities.GetQuantityByName(quantityName);
+ Enum fromUnit = unitParser.Parse(fromUnitAbbrev, quantityInfo.UnitType, formatProvider); // ex: ("m", LengthUnit) => LengthUnit.Meter
+ Enum toUnit = unitParser.Parse(toUnitAbbrev, quantityInfo.UnitType, formatProvider); // ex:("cm", LengthUnit) => LengthUnit.Centimeter
+ return Quantity.From(fromValue, fromUnit).As(toUnit);
}
///
@@ -458,7 +486,7 @@ public static double ConvertByAbbreviation(double fromValue, string quantityName
/// True if conversion was successful.
public static bool TryConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, out double result)
{
- return TryConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, out result, null);
+ return TryConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, out result, (IFormatProvider?)null);
}
///
@@ -480,75 +508,56 @@ public static bool TryConvertByAbbreviation(double fromValue, string quantityNam
/// 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 the IFormatProvider.")]
public static bool TryConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, out double result,
string? culture)
{
- result = 0d;
-
- if (!TryGetUnitType(quantityName, out Type? unitType))
- return false;
-
- var cultureInfo = CultureHelper.GetCultureOrInvariant(culture);
-
- if (!UnitsNetSetup.Default.UnitParser.TryParse(fromUnitAbbrev, unitType, cultureInfo, out Enum? fromUnit)) // ex: ("m", LengthUnit) => LengthUnit.Meter
- return false;
-
- if (!UnitsNetSetup.Default.UnitParser.TryParse(toUnitAbbrev, unitType, cultureInfo, out Enum? toUnit)) // ex:("cm", LengthUnit) => LengthUnit.Centimeter
- return false;
-
- var fromQuantity = Quantity.From(fromValue, fromUnit);
- result = fromQuantity.As(toUnit);
-
- return true;
- }
-
- ///
- /// Try to parse a unit by the unit enum type and a unit enum value >
- ///
- /// Unit type, such as .
- /// Unit name, such as "Meter" corresponding to .
- /// The return enum value, such as boxed as an object.
- /// True if succeeded, otherwise false.
- /// No unit values match the .
- // TODO Move to Quantity.
- internal static bool TryParseUnit(Type unitType, string unitName, [NotNullWhen(true)] out Enum? unitValue)
- {
- unitValue = null;
- var eNames = Enum.GetNames(unitType);
- var matchedUnitName = eNames.FirstOrDefault(x => x.Equals(unitName, StringComparison.OrdinalIgnoreCase));
- if (matchedUnitName == null)
- return false;
-
- unitValue = (Enum) Enum.Parse(unitType, matchedUnitName);
- return true;
+ return TryConvertByAbbreviation(fromValue, quantityName, fromUnitAbbrev, toUnitAbbrev, out result, CultureHelper.GetCultureOrInvariant(culture));
}
///
- /// Try to parse a unit enum value by its quantity and unit names.
+ /// 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 invariant unit enum name, such as "Meter". Does not support localization.
- /// The return enum value, such as boxed as an object.
- /// True if succeeded, otherwise false.
- /// No unit values match the .
- // TODO Move to Quantity.
- internal static bool TryParseUnit(string quantityName, string unitName, [NotNullWhen(true)] out Enum? unitValue)
+ /// 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 format provider to use for lookup. 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 static bool TryConvertByAbbreviation(double fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, out double result,
+ IFormatProvider? formatProvider)
{
- unitValue = default;
+ QuantityInfoLookup quantities = UnitsNetSetup.Default.QuantityInfoLookup;
+ UnitParser unitParser = UnitsNetSetup.Default.UnitParser;
+ if (!quantities.TryGetQuantityByName(quantityName, out QuantityInfo? quantityInfo) )
+ {
+ result = 0;
+ return false;
+ }
- // Get enum type for unit of this quantity, f.ex. LengthUnit for quantity Length.
- // Then try to parse the unit enum value.
- return TryGetUnitType(quantityName, out Type? unitType) &&
- TryParseUnit(unitType, unitName, out unitValue);
- }
+ if (!unitParser.TryParse(fromUnitAbbrev, quantityInfo.UnitType, formatProvider, out Enum? fromUnit) ||
+ !unitParser.TryParse(toUnitAbbrev, quantityInfo.UnitType, formatProvider, out Enum? toUnit))
+ {
+ result = 0;
+ return false;
+ }
- // TODO Move to Quantity.
- internal static bool TryGetUnitType(string quantityName, [NotNullWhen(true)] out Type? unitType)
- {
- var quantityInfo = Quantity.Infos.FirstOrDefault(info => info.Name.Equals(quantityName, StringComparison.OrdinalIgnoreCase));
+ result = Quantity.From(fromValue, fromUnit).As(toUnit);
+ return true;
- unitType = quantityInfo?.UnitType;
- return quantityInfo != null;
}
}
}
diff --git a/UnitsNet/UnitFormatter.cs b/UnitsNet/UnitFormatter.cs
index 18f9979c72..df359557e6 100644
--- a/UnitsNet/UnitFormatter.cs
+++ b/UnitsNet/UnitFormatter.cs
@@ -73,7 +73,7 @@ private static bool NearlyEqual(double a, double b)
public static object[] GetFormatArgs(TUnitType unit, double value, IFormatProvider? culture, IEnumerable
public string? QuantityName { get; }
+
+ ///
+ /// Gets the unique key representing the unit type and its corresponding value.
+ ///
+ ///
+ /// This key is particularly useful when using an enum-based unit in a hash-based collection,
+ /// as it avoids the boxing that would normally occur when casting the enum to .
+ ///
+ public virtual UnitKey UnitKey => Value;
}
///
@@ -81,6 +91,7 @@ public UnitInfo(Enum value, string pluralName, BaseUnits baseUnits, string quant
/// or dynamically via .
///
/// The unit enum type, such as .
+ [DebuggerDisplay("{Name} ({Value})")]
public class UnitInfo : UnitInfo
where TUnit : struct, Enum
{
@@ -101,5 +112,11 @@ public UnitInfo(TUnit value, string pluralName, BaseUnits baseUnits, string quan
///
public new TUnit Value { get; }
+
+ ///
+ public override UnitKey UnitKey
+ {
+ get => UnitKey.ForUnit(Value);
+ }
}
}
diff --git a/UnitsNet/UnitsNet.csproj b/UnitsNet/UnitsNet.csproj
index 186c8a04d8..8c7d8299a7 100644
--- a/UnitsNet/UnitsNet.csproj
+++ b/UnitsNet/UnitsNet.csproj
@@ -53,4 +53,7 @@
+
+
+