Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add UnitsNetSetup to hold global static state #2

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions UnitsNet/CustomCode/Quantity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,7 @@ namespace UnitsNet
{
public partial class Quantity
{
static Quantity()
{
Default = new QuantityInfoLookup();
}

private static QuantityInfoLookup Default
{
get;
}
private static QuantityInfoLookup Default => UnitsNetSetup.Default.QuantityInfoLookup;

/// <summary>
/// All enum value names of <see cref="Infos"/>, such as "Length" and "Mass".
Expand All @@ -39,7 +31,7 @@ public static bool TryGetUnitInfo(Enum unitEnum, [NotNullWhen(true)] out UnitInf
Default.TryGetUnitInfo(unitEnum, out unitInfo);

/// <summary>
///
///
/// </summary>
/// <param name="unit"></param>
/// <param name="unitInfo"></param>
Expand Down
8 changes: 2 additions & 6 deletions UnitsNet/CustomCode/QuantityParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,15 @@ internal class QuantityParser
private readonly UnitAbbreviationsCache _unitAbbreviationsCache;
private readonly UnitParser _unitParser;

public static QuantityParser Default { get; }
[Obsolete("Use UnitsNetSetup.Default.Parser instead.")]
public static QuantityParser Default => UnitsNetSetup.Default.QuantityParser;

public QuantityParser(UnitAbbreviationsCache? unitAbbreviationsCache)
{
_unitAbbreviationsCache = unitAbbreviationsCache ?? UnitAbbreviationsCache.Default;
_unitParser = new UnitParser(_unitAbbreviationsCache);
}

static QuantityParser()
{
Default = new QuantityParser(UnitAbbreviationsCache.Default);
}

[SuppressMessage("ReSharper", "UseStringInterpolation")]
internal TQuantity Parse<TQuantity, TUnitType>(string str,
IFormatProvider? formatProvider,
Expand Down
33 changes: 29 additions & 4 deletions UnitsNet/CustomCode/UnitAbbreviationsCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,48 @@ public sealed class UnitAbbreviationsCache
/// <summary>
/// The static instance used internally for ToString() and Parse() of quantities and units.
/// </summary>
public static UnitAbbreviationsCache Default { get; }
[Obsolete("Use UnitsNetSetup.Default.UnitAbbreviations instead.")]
public static UnitAbbreviationsCache Default => UnitsNetSetup.Default.UnitAbbreviations;

private QuantityInfoLookup QuantityInfoLookup { get; }

/// <summary>
/// Create an instance of the cache and load all the abbreviations defined in the library.
/// </summary>
// TODO Change this to create an empty cache in v6: https://github.com/angularsen/UnitsNet/issues/1200
[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.")]
public UnitAbbreviationsCache()
: this(new QuantityInfoLookup(Quantity.ByName.Values))
{
QuantityInfoLookup= new QuantityInfoLookup();
}

static UnitAbbreviationsCache()
/// <summary>
/// Creates an instance of the cache and load all the abbreviations defined in the library.
/// </summary>
/// <remarks>
/// Access type is <c>internal</c> until this class is matured and ready for external use.
/// </remarks>
internal UnitAbbreviationsCache(QuantityInfoLookup quantityInfoLookup)
{
Default = new UnitAbbreviationsCache();
QuantityInfoLookup = quantityInfoLookup;
}

/// <summary>
/// Create an instance with empty cache.
/// </summary>
/// <remarks>
/// Workaround until v6 changes the default ctor to create an empty cache.<br/>
/// </remarks>
/// <returns>Instance with empty cache.</returns>
// TODO Remove in v6: https://github.com/angularsen/UnitsNet/issues/1200
public static UnitAbbreviationsCache CreateEmpty() => new(new QuantityInfoLookup(new List<QuantityInfo>()));

/// <summary>
/// Create an instance of the cache and load all the built-in unit abbreviations defined in the library.
/// </summary>
/// <returns>Instance with default abbreviations cache.</returns>
public static UnitAbbreviationsCache CreateDefault() => new(new QuantityInfoLookup(Quantity.ByName.Values));

/// <summary>
/// Adds one or more unit abbreviation for the given unit enum value.
/// 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
Expand Down
9 changes: 3 additions & 6 deletions UnitsNet/CustomCode/UnitParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,19 @@ public sealed class UnitParser
/// The default static instance used internally to parse quantities and units using the
/// default abbreviations cache for all units and abbreviations defined in the library.
/// </summary>
public static UnitParser Default { get; }
[Obsolete("Use UnitsNetSetup.Default.UnitParser instead.")]
public static UnitParser Default => UnitsNetSetup.Default.UnitParser;

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

static UnitParser()
{
Default = new UnitParser(UnitAbbreviationsCache.Default);
}

/// <summary>
/// Parses a unit abbreviation for a given unit enumeration type.
/// Example: Parse&lt;LengthUnit&gt;("km") => LengthUnit.Kilometer
Expand Down
85 changes: 85 additions & 0 deletions UnitsNet/CustomCode/UnitsNetSetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Licensed under MIT No Attribution, see LICENSE file at the root.
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.

using System.Collections.Generic;
using UnitsNet.Units;

namespace UnitsNet;

/// <summary>
/// UnitsNet setup of quantities, units, unit abbreviations and conversion functions.<br />
/// <br />
/// For normal use, <see cref="Default" /> is used and can be manipulated to add new units or change default unit
/// abbreviations.<br />
/// Alternatively, a setup instance may be provided for most static methods, such as
/// <see cref="Quantity.Parse(System.Type,string)" /> and
/// <see cref="QuantityFormatter.Format{TUnitType}(UnitsNet.IQuantity{TUnitType},string)" />.
/// </summary>
public sealed class UnitsNetSetup
{
static UnitsNetSetup()
{
var unitConverter = UnitConverter.CreateDefault();
ICollection<QuantityInfo> quantityInfos = Quantity.ByName.Values;

Default = new UnitsNetSetup(quantityInfos, unitConverter);
}

/// <summary>
/// Create a new UnitsNet setup with the given quantities, their units and unit conversion functions between units.
/// </summary>
/// <param name="quantityInfos">The quantities and their units to support for unit conversions, Parse() and ToString().</param>
/// <param name="unitConverter">The unit converter instance.</param>
public UnitsNetSetup(ICollection<QuantityInfo> quantityInfos, UnitConverter unitConverter)
{
var quantityInfoLookup = new QuantityInfoLookup(quantityInfos);
var unitAbbreviations = new UnitAbbreviationsCache(quantityInfoLookup);

UnitConverter = unitConverter;
UnitAbbreviations = unitAbbreviations;
UnitParser = new UnitParser(unitAbbreviations);
QuantityParser = new QuantityParser(unitAbbreviations);
QuantityInfoLookup = quantityInfoLookup;
}

/// <summary>
/// The global default UnitsNet setup of quantities, units, unit abbreviations and conversion functions.
/// This setup is used by default in static Parse and ToString methods of quantities unless a setup instance is
/// provided.
/// </summary>
/// <remarks>
/// Manipulating this instance, such as adding new units or changing default unit abbreviations, will affect most
/// usages of UnitsNet in the
/// current AppDomain since the typical use is via static members and not providing a setup instance.
/// </remarks>
public static UnitsNetSetup Default { get; }

/// <summary>
/// Converts between units of a quantity, such as from meters to centimeters of a given length.
/// </summary>
public UnitConverter UnitConverter { get; }

/// <summary>
/// Maps unit enums to unit abbreviation strings for one or more cultures, used by ToString() and Parse() methods of
/// quantities.
/// </summary>
public UnitAbbreviationsCache UnitAbbreviations { get; }

/// <summary>
/// Parses units from strings, such as <see cref="LengthUnit.Centimeter" /> from "cm".
/// </summary>
public UnitParser UnitParser { get; }

/// <summary>
/// Parses quantities from strings, such as parsing <see cref="Mass" /> from "1.2 kg".
/// </summary>
internal QuantityParser QuantityParser { get; }

/// <summary>
/// The quantities and units that are loaded.
/// </summary>
/// <remarks>
/// Access type is <c>internal</c> until this class is matured and ready for external use.
/// </remarks>
internal QuantityInfoLookup QuantityInfoLookup { get; }
}
39 changes: 24 additions & 15 deletions UnitsNet/QuantityInfoLookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,27 @@ namespace UnitsNet
/// <summary>
/// A collection of <see cref="QuantityInfo"/>.
/// </summary>
public class QuantityInfoLookup
/// <remarks>
/// Access type is <c>internal</c> until this class is matured and ready for external use.
/// </remarks>
internal class QuantityInfoLookup
{
private readonly Lazy<QuantityInfo[]> InfosLazy;
private readonly Lazy<Dictionary<(Type, string), UnitInfo>> UnitTypeAndNameToUnitInfoLazy;
private readonly Lazy<QuantityInfo[]> _infosLazy;
private readonly Lazy<Dictionary<(Type, string), UnitInfo>> _unitTypeAndNameToUnitInfoLazy;

/// <summary>
/// New instance.
/// </summary>
public QuantityInfoLookup()
/// <param name="quantityInfos"></param>
public QuantityInfoLookup(ICollection<QuantityInfo> quantityInfos)
{
ICollection<QuantityInfo> quantityInfos = Quantity.ByName.Values;
Names = quantityInfos.Select(qt => qt.Name).ToArray();

InfosLazy = new Lazy<QuantityInfo[]>(() => quantityInfos
_infosLazy = new Lazy<QuantityInfo[]>(() => quantityInfos
.OrderBy(quantityInfo => quantityInfo.Name)
.ToArray());

UnitTypeAndNameToUnitInfoLazy = new Lazy<Dictionary<(Type, string), UnitInfo>>(() =>
_unitTypeAndNameToUnitInfoLazy = new Lazy<Dictionary<(Type, string), UnitInfo>>(() =>
{
return Infos
.SelectMany(quantityInfo => quantityInfo.UnitInfos
Expand All @@ -45,27 +48,27 @@ public QuantityInfoLookup()
/// <summary>
/// All quantity information objects, such as <see cref="Length.Info"/> and <see cref="Mass.Info"/>.
/// </summary>
public QuantityInfo[] Infos => InfosLazy.Value;
public QuantityInfo[] Infos => _infosLazy.Value;

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

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

/// <summary>
///
///
/// </summary>
/// <param name="unit"></param>
/// <param name="unitInfo"></param>
public void AddUnitInfo(Enum unit, UnitInfo unitInfo)
{
UnitTypeAndNameToUnitInfoLazy.Value.Add((unit.GetType(), unit.ToString()), unitInfo);
_unitTypeAndNameToUnitInfoLazy.Value.Add((unit.GetType(), unit.ToString()), unitInfo);
}

/// <summary>
Expand All @@ -77,6 +80,7 @@ public void AddUnitInfo(Enum unit, UnitInfo unitInfo)
/// <exception cref="ArgumentException">Unit value is not a know unit enum type.</exception>
public IQuantity From(QuantityValue value, Enum unit)
{
// TODO Support custom units, currently only hardcoded built-in quantities are supported.
if (Quantity.TryFrom(value, unit, out IQuantity? quantity))
return quantity;

Expand All @@ -95,6 +99,7 @@ public bool TryFrom(double value, Enum unit, [NotNullWhen(true)] out IQuantity?
return false;
}

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

Expand All @@ -114,23 +119,27 @@ public IQuantity Parse(IFormatProvider? formatProvider, Type quantityType, strin
if (!typeof(IQuantity).IsAssignableFrom(quantityType))
throw new ArgumentException($"Type {quantityType} must be of type UnitsNet.IQuantity.");

// 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 ArgumentException($"Quantity string could not be parsed to quantity {quantityType}.");
}

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

/// <summary>
/// Get a list of quantities that has the given base dimensions.
/// </summary>
/// <param name="baseDimensions">The base dimensions to match.</param>
public IEnumerable<QuantityInfo> GetQuantitiesWithBaseDimensions(BaseDimensions baseDimensions)
{
return InfosLazy.Value.Where(info => info.BaseDimensions.Equals(baseDimensions));
return _infosLazy.Value.Where(info => info.BaseDimensions.Equals(baseDimensions));
}
}
}
21 changes: 14 additions & 7 deletions UnitsNet/UnitConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,8 @@ public sealed class UnitConverter
/// The static instance used by Units.NET to convert between units. Modify this to add/remove conversion functions at runtime, such
/// as adding your own third-party units and quantities to convert between.
/// </summary>
public static UnitConverter Default { get; }

static UnitConverter()
{
Default = new UnitConverter();
RegisterDefaultConversions(Default);
}
[Obsolete("Use UnitsNetSetup.Default.UnitConverter instead.")]
public static UnitConverter Default => UnitsNetSetup.Default.UnitConverter;

/// <summary>
/// Creates a new <see cref="UnitConverter"/> instance.
Expand All @@ -63,6 +58,18 @@ public UnitConverter(UnitConverter other)
ConversionFunctions = new ConcurrentDictionary<ConversionFunctionLookupKey, ConversionFunction>(other.ConversionFunctions);
}

/// <summary>
/// Create an instance of the unit converter with all the built-in unit conversions defined in the library.
/// </summary>
/// <returns>The unit converter.</returns>
public static UnitConverter CreateDefault()
{
var unitConverter = new UnitConverter();
RegisterDefaultConversions(unitConverter);

return unitConverter;
}

private ConcurrentDictionary<ConversionFunctionLookupKey, ConversionFunction> ConversionFunctions
{
get;
Expand Down