Skip to content

Commit f540d8d

Browse files
committed
Add UnitsNetSetup to hold global static state
Fix static init racing condition by replacing {get;} with =>
1 parent 439c143 commit f540d8d

7 files changed

+157
-46
lines changed

UnitsNet/CustomCode/Quantity.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,7 @@ namespace UnitsNet
99
{
1010
public partial class Quantity
1111
{
12-
static Quantity()
13-
{
14-
Default = new QuantityInfoLookup();
15-
}
16-
17-
private static QuantityInfoLookup Default
18-
{
19-
get;
20-
}
12+
private static QuantityInfoLookup Default => UnitsNetSetup.Default.QuantityInfoLookup;
2113

2214
/// <summary>
2315
/// All enum value names of <see cref="Infos"/>, such as "Length" and "Mass".

UnitsNet/CustomCode/QuantityParser.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public class QuantityParser
3737
/// <summary>
3838
/// The default instance of <see cref="QuantityParser"/>, which uses <see cref="UnitAbbreviationsCache.Default"/> unit abbreviations.
3939
/// </summary>
40-
public static QuantityParser Default { get; }
40+
[Obsolete("Use UnitsNetSetup.Default.QuantityParser instead.")]
41+
public static QuantityParser Default => UnitsNetSetup.Default.QuantityParser;
4142

4243
/// <summary>
4344
/// Creates an instance of <see cref="QuantityParser"/>, optionally specifying an <see cref="UnitAbbreviationsCache"/>
@@ -50,11 +51,6 @@ public QuantityParser(UnitAbbreviationsCache? unitAbbreviationsCache = null)
5051
_unitParser = new UnitParser(_unitAbbreviationsCache);
5152
}
5253

53-
static QuantityParser()
54-
{
55-
Default = new QuantityParser(UnitAbbreviationsCache.Default);
56-
}
57-
5854
/// <summary>
5955
/// Parses a quantity from a string, such as "1.2 kg" to <see cref="Length"/> or "100 cm" to <see cref="Mass"/>.
6056
/// </summary>

UnitsNet/CustomCode/UnitAbbreviationsCache.cs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,48 @@ public sealed class UnitAbbreviationsCache
3030
/// <summary>
3131
/// The static instance used internally for ToString() and Parse() of quantities and units.
3232
/// </summary>
33-
public static UnitAbbreviationsCache Default { get; }
33+
[Obsolete("Use UnitsNetSetup.Default.UnitAbbreviations instead.")]
34+
public static UnitAbbreviationsCache Default => UnitsNetSetup.Default.UnitAbbreviations;
3435

3536
private QuantityInfoLookup QuantityInfoLookup { get; }
3637

3738
/// <summary>
3839
/// Create an instance of the cache and load all the abbreviations defined in the library.
3940
/// </summary>
41+
// TODO Change this to create an empty cache in v6: https://github.com/angularsen/UnitsNet/issues/1200
42+
[Obsolete("Use CreateDefault() instead to create an instance that loads the built-in units. The default ctor will change to create an empty cache in UnitsNet v6.")]
4043
public UnitAbbreviationsCache()
44+
: this(new QuantityInfoLookup(Quantity.ByName.Values))
4145
{
42-
QuantityInfoLookup= new QuantityInfoLookup();
4346
}
4447

45-
static UnitAbbreviationsCache()
48+
/// <summary>
49+
/// Creates an instance of the cache and load all the abbreviations defined in the library.
50+
/// </summary>
51+
/// <remarks>
52+
/// Access type is <c>internal</c> until this class is matured and ready for external use.
53+
/// </remarks>
54+
internal UnitAbbreviationsCache(QuantityInfoLookup quantityInfoLookup)
4655
{
47-
Default = new UnitAbbreviationsCache();
56+
QuantityInfoLookup = quantityInfoLookup;
4857
}
4958

59+
/// <summary>
60+
/// Create an instance with empty cache.
61+
/// </summary>
62+
/// <remarks>
63+
/// Workaround until v6 changes the default ctor to create an empty cache.<br/>
64+
/// </remarks>
65+
/// <returns>Instance with empty cache.</returns>
66+
// TODO Remove in v6: https://github.com/angularsen/UnitsNet/issues/1200
67+
public static UnitAbbreviationsCache CreateEmpty() => new(new QuantityInfoLookup(new List<QuantityInfo>()));
68+
69+
/// <summary>
70+
/// Create an instance of the cache and load all the built-in unit abbreviations defined in the library.
71+
/// </summary>
72+
/// <returns>Instance with default abbreviations cache.</returns>
73+
public static UnitAbbreviationsCache CreateDefault() => new(new QuantityInfoLookup(Quantity.ByName.Values));
74+
5075
/// <summary>
5176
/// Adds one or more unit abbreviation for the given unit enum value.
5277
/// This is used to dynamically add abbreviations for existing unit enums such as <see cref="UnitsNet.Units.LengthUnit"/> or to extend with third-party unit enums

UnitsNet/CustomCode/UnitParser.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,19 @@ public sealed class UnitParser
2323
/// The default static instance used internally to parse quantities and units using the
2424
/// default abbreviations cache for all units and abbreviations defined in the library.
2525
/// </summary>
26-
public static UnitParser Default { get; }
26+
[Obsolete("Use UnitsNetSetup.Default.UnitParser instead.")]
27+
public static UnitParser Default => UnitsNetSetup.Default.UnitParser;
2728

2829
/// <summary>
2930
/// Create a parser using the given unit abbreviations cache.
3031
/// </summary>
3132
/// <param name="unitAbbreviationsCache"></param>
33+
// TODO Change this to not fallback to built-in units abbreviations when given null, in v6: https://github.com/angularsen/UnitsNet/issues/1200
3234
public UnitParser(UnitAbbreviationsCache? unitAbbreviationsCache)
3335
{
3436
_unitAbbreviationsCache = unitAbbreviationsCache ?? UnitAbbreviationsCache.Default;
3537
}
3638

37-
static UnitParser()
38-
{
39-
Default = new UnitParser(UnitAbbreviationsCache.Default);
40-
}
41-
4239
/// <summary>
4340
/// Parses a unit abbreviation for a given unit enumeration type.
4441
/// Example: Parse&lt;LengthUnit&gt;("km") => LengthUnit.Kilometer

UnitsNet/CustomCode/UnitsNetSetup.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Licensed under MIT No Attribution, see LICENSE file at the root.
2+
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
3+
4+
using System.Collections.Generic;
5+
using UnitsNet.Units;
6+
7+
namespace UnitsNet;
8+
9+
/// <summary>
10+
/// UnitsNet setup of quantities, units, unit abbreviations and conversion functions.<br />
11+
/// <br />
12+
/// For normal use, <see cref="Default" /> is used and can be manipulated to add new units or change default unit
13+
/// abbreviations.<br />
14+
/// Alternatively, a setup instance may be provided for most static methods, such as
15+
/// <see cref="Quantity.Parse(System.Type,string)" /> and
16+
/// <see cref="QuantityFormatter.Format{TUnitType}(UnitsNet.IQuantity{TUnitType},string)" />.
17+
/// </summary>
18+
public sealed class UnitsNetSetup
19+
{
20+
static UnitsNetSetup()
21+
{
22+
var unitConverter = UnitConverter.CreateDefault();
23+
ICollection<QuantityInfo> quantityInfos = Quantity.ByName.Values;
24+
25+
Default = new UnitsNetSetup(quantityInfos, unitConverter);
26+
}
27+
28+
/// <summary>
29+
/// Create a new UnitsNet setup with the given quantities, their units and unit conversion functions between units.
30+
/// </summary>
31+
/// <param name="quantityInfos">The quantities and their units to support for unit conversions, Parse() and ToString().</param>
32+
/// <param name="unitConverter">The unit converter instance.</param>
33+
public UnitsNetSetup(ICollection<QuantityInfo> quantityInfos, UnitConverter unitConverter)
34+
{
35+
var quantityInfoLookup = new QuantityInfoLookup(quantityInfos);
36+
var unitAbbreviations = new UnitAbbreviationsCache(quantityInfoLookup);
37+
38+
UnitConverter = unitConverter;
39+
UnitAbbreviations = unitAbbreviations;
40+
UnitParser = new UnitParser(unitAbbreviations);
41+
QuantityParser = new QuantityParser(unitAbbreviations);
42+
QuantityInfoLookup = quantityInfoLookup;
43+
}
44+
45+
/// <summary>
46+
/// The global default UnitsNet setup of quantities, units, unit abbreviations and conversion functions.
47+
/// This setup is used by default in static Parse and ToString methods of quantities unless a setup instance is
48+
/// provided.
49+
/// </summary>
50+
/// <remarks>
51+
/// Manipulating this instance, such as adding new units or changing default unit abbreviations, will affect most
52+
/// usages of UnitsNet in the
53+
/// current AppDomain since the typical use is via static members and not providing a setup instance.
54+
/// </remarks>
55+
public static UnitsNetSetup Default { get; }
56+
57+
/// <summary>
58+
/// Converts between units of a quantity, such as from meters to centimeters of a given length.
59+
/// </summary>
60+
public UnitConverter UnitConverter { get; }
61+
62+
/// <summary>
63+
/// Maps unit enums to unit abbreviation strings for one or more cultures, used by ToString() and Parse() methods of
64+
/// quantities.
65+
/// </summary>
66+
public UnitAbbreviationsCache UnitAbbreviations { get; }
67+
68+
/// <summary>
69+
/// Parses units from strings, such as <see cref="LengthUnit.Centimeter" /> from "cm".
70+
/// </summary>
71+
public UnitParser UnitParser { get; }
72+
73+
/// <summary>
74+
/// Parses quantities from strings, such as parsing <see cref="Mass" /> from "1.2 kg".
75+
/// </summary>
76+
internal QuantityParser QuantityParser { get; }
77+
78+
/// <summary>
79+
/// The quantities and units that are loaded.
80+
/// </summary>
81+
/// <remarks>
82+
/// Access type is <c>internal</c> until this class is matured and ready for external use.
83+
/// </remarks>
84+
internal QuantityInfoLookup QuantityInfoLookup { get; }
85+
}

UnitsNet/QuantityInfoLookup.cs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,27 @@ namespace UnitsNet
99
/// <summary>
1010
/// A collection of <see cref="QuantityInfo"/>.
1111
/// </summary>
12-
public class QuantityInfoLookup
12+
/// <remarks>
13+
/// Access type is <c>internal</c> until this class is matured and ready for external use.
14+
/// </remarks>
15+
internal class QuantityInfoLookup
1316
{
14-
private readonly Lazy<QuantityInfo[]> InfosLazy;
15-
private readonly Lazy<Dictionary<(Type, string), UnitInfo>> UnitTypeAndNameToUnitInfoLazy;
17+
private readonly Lazy<QuantityInfo[]> _infosLazy;
18+
private readonly Lazy<Dictionary<(Type, string), UnitInfo>> _unitTypeAndNameToUnitInfoLazy;
1619

1720
/// <summary>
1821
/// New instance.
1922
/// </summary>
20-
public QuantityInfoLookup()
23+
/// <param name="quantityInfos"></param>
24+
public QuantityInfoLookup(ICollection<QuantityInfo> quantityInfos)
2125
{
22-
ICollection<QuantityInfo> quantityInfos = Quantity.ByName.Values;
2326
Names = quantityInfos.Select(qt => qt.Name).ToArray();
2427

25-
InfosLazy = new Lazy<QuantityInfo[]>(() => quantityInfos
28+
_infosLazy = new Lazy<QuantityInfo[]>(() => quantityInfos
2629
.OrderBy(quantityInfo => quantityInfo.Name)
2730
.ToArray());
2831

29-
UnitTypeAndNameToUnitInfoLazy = new Lazy<Dictionary<(Type, string), UnitInfo>>(() =>
32+
_unitTypeAndNameToUnitInfoLazy = new Lazy<Dictionary<(Type, string), UnitInfo>>(() =>
3033
{
3134
return Infos
3235
.SelectMany(quantityInfo => quantityInfo.UnitInfos
@@ -45,18 +48,18 @@ public QuantityInfoLookup()
4548
/// <summary>
4649
/// All quantity information objects, such as <see cref="Length.Info"/> and <see cref="Mass.Info"/>.
4750
/// </summary>
48-
public QuantityInfo[] Infos => InfosLazy.Value;
51+
public QuantityInfo[] Infos => _infosLazy.Value;
4952

5053
/// <summary>
5154
/// Get <see cref="UnitInfo"/> for a given unit enum value.
5255
/// </summary>
53-
public UnitInfo GetUnitInfo(Enum unitEnum) => UnitTypeAndNameToUnitInfoLazy.Value[(unitEnum.GetType(), unitEnum.ToString())];
56+
public UnitInfo GetUnitInfo(Enum unitEnum) => _unitTypeAndNameToUnitInfoLazy.Value[(unitEnum.GetType(), unitEnum.ToString())];
5457

5558
/// <summary>
5659
/// Try to get <see cref="UnitInfo"/> for a given unit enum value.
5760
/// </summary>
5861
public bool TryGetUnitInfo(Enum unitEnum, [NotNullWhen(true)] out UnitInfo? unitInfo) =>
59-
UnitTypeAndNameToUnitInfoLazy.Value.TryGetValue((unitEnum.GetType(), unitEnum.ToString()), out unitInfo);
62+
_unitTypeAndNameToUnitInfoLazy.Value.TryGetValue((unitEnum.GetType(), unitEnum.ToString()), out unitInfo);
6063

6164
/// <summary>
6265
///
@@ -65,7 +68,7 @@ public bool TryGetUnitInfo(Enum unitEnum, [NotNullWhen(true)] out UnitInfo? unit
6568
/// <param name="unitInfo"></param>
6669
public void AddUnitInfo(Enum unit, UnitInfo unitInfo)
6770
{
68-
UnitTypeAndNameToUnitInfoLazy.Value.Add((unit.GetType(), unit.ToString()), unitInfo);
71+
_unitTypeAndNameToUnitInfoLazy.Value.Add((unit.GetType(), unit.ToString()), unitInfo);
6972
}
7073

7174
/// <summary>
@@ -77,6 +80,7 @@ public void AddUnitInfo(Enum unit, UnitInfo unitInfo)
7780
/// <exception cref="ArgumentException">Unit value is not a know unit enum type.</exception>
7881
public IQuantity From(QuantityValue value, Enum unit)
7982
{
83+
// TODO Support custom units, currently only hardcoded built-in quantities are supported.
8084
return Quantity.TryFrom(value, unit, out IQuantity? quantity)
8185
? quantity
8286
: 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?");
@@ -93,6 +97,7 @@ public bool TryFrom(double value, Enum unit, [NotNullWhen(true)] out IQuantity?
9397
return false;
9498
}
9599

100+
// TODO Support custom units, currently only hardcoded built-in quantities are supported.
96101
return Quantity.TryFrom((QuantityValue)value, unit, out quantity);
97102
}
98103

@@ -112,23 +117,27 @@ public IQuantity Parse(IFormatProvider? formatProvider, Type quantityType, strin
112117
if (!typeof(IQuantity).IsAssignableFrom(quantityType))
113118
throw new ArgumentException($"Type {quantityType} must be of type UnitsNet.IQuantity.");
114119

120+
// TODO Support custom units, currently only hardcoded built-in quantities are supported.
115121
if (Quantity.TryParse(formatProvider, quantityType, quantityString, out IQuantity? quantity))
116122
return quantity;
117123

118124
throw new UnitNotFoundException($"Quantity string '{quantityString}' could not be parsed to quantity '{quantityType}'.");
119125
}
120126

121127
/// <inheritdoc cref="Quantity.TryParse(IFormatProvider,System.Type,string,out UnitsNet.IQuantity)"/>
122-
public bool TryParse(Type quantityType, string quantityString, [NotNullWhen(true)] out IQuantity? quantity) =>
123-
Quantity.TryParse(null, quantityType, quantityString, out quantity);
128+
public bool TryParse(Type quantityType, string quantityString, [NotNullWhen(true)] out IQuantity? quantity)
129+
{
130+
// TODO Support custom units, currently only hardcoded built-in quantities are supported.
131+
return Quantity.TryParse(null, quantityType, quantityString, out quantity);
132+
}
124133

125134
/// <summary>
126135
/// Get a list of quantities that has the given base dimensions.
127136
/// </summary>
128137
/// <param name="baseDimensions">The base dimensions to match.</param>
129138
public IEnumerable<QuantityInfo> GetQuantitiesWithBaseDimensions(BaseDimensions baseDimensions)
130139
{
131-
return InfosLazy.Value.Where(info => info.BaseDimensions.Equals(baseDimensions));
140+
return _infosLazy.Value.Where(info => info.BaseDimensions.Equals(baseDimensions));
132141
}
133142
}
134143
}

UnitsNet/UnitConverter.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,8 @@ public sealed class UnitConverter
3939
/// The static instance used by Units.NET to convert between units. Modify this to add/remove conversion functions at runtime, such
4040
/// as adding your own third-party units and quantities to convert between.
4141
/// </summary>
42-
public static UnitConverter Default { get; }
43-
44-
static UnitConverter()
45-
{
46-
Default = new UnitConverter();
47-
RegisterDefaultConversions(Default);
48-
}
42+
[Obsolete("Use UnitsNetSetup.Default.UnitConverter instead.")]
43+
public static UnitConverter Default => UnitsNetSetup.Default.UnitConverter;
4944

5045
/// <summary>
5146
/// Creates a new <see cref="UnitConverter"/> instance.
@@ -64,6 +59,18 @@ public UnitConverter(UnitConverter other)
6459
ConversionFunctions = new ConcurrentDictionary<ConversionFunctionLookupKey, ConversionFunction>(other.ConversionFunctions);
6560
}
6661

62+
/// <summary>
63+
/// Create an instance of the unit converter with all the built-in unit conversions defined in the library.
64+
/// </summary>
65+
/// <returns>The unit converter.</returns>
66+
public static UnitConverter CreateDefault()
67+
{
68+
var unitConverter = new UnitConverter();
69+
RegisterDefaultConversions(unitConverter);
70+
71+
return unitConverter;
72+
}
73+
6774
private ConcurrentDictionary<ConversionFunctionLookupKey, ConversionFunction> ConversionFunctions
6875
{
6976
get;

0 commit comments

Comments
 (0)